@carllee1983/dbcli 2.1.0 → 3.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.
- package/.cursor/skills/dbcli/reference.md +7 -4
- package/.github/skills/dbcli/reference.md +7 -4
- package/CHANGELOG.md +36 -0
- package/assets/reference.md +7 -4
- package/dist/cli-runtime.mjs +127 -66
- package/dist/cli.mjs +2 -1
- package/package.json +2 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +7 -4
- package/skills/dbcli/reference.md +7 -4
|
@@ -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
|
-
> **
|
|
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" \
|
|
@@ -2721,7 +2721,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2721
2721
|
|
|
2722
2722
|
| Flag | Purpose | Default |
|
|
2723
2723
|
|---|---|---|
|
|
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. | — |
|
|
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. 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
2725
|
| `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
|
|
2726
2726
|
| `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
|
|
2727
2727
|
| `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
|
|
@@ -2729,8 +2729,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2729
2729
|
| `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
|
|
2730
2730
|
|
|
2731
2731
|
At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
|
|
2732
|
-
|
|
2733
|
-
|
|
2732
|
+
carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
|
|
2733
|
+
composing the same claims and references twice produces the same pack. `createdAt` records when the
|
|
2734
|
+
pack was written and is deliberately outside the digest — it is the one field a restamp can change
|
|
2735
|
+
without breaking validation. Whether a referenced source is still resolvable is reported by
|
|
2736
|
+
`evidence validate`, not stored in the pack.
|
|
2734
2737
|
|
|
2735
2738
|
#### `evidence validate`
|
|
2736
2739
|
|
|
@@ -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
|
-
> **
|
|
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" \
|
|
@@ -2721,7 +2721,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2721
2721
|
|
|
2722
2722
|
| Flag | Purpose | Default |
|
|
2723
2723
|
|---|---|---|
|
|
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. | — |
|
|
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. 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
2725
|
| `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
|
|
2726
2726
|
| `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
|
|
2727
2727
|
| `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
|
|
@@ -2729,8 +2729,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2729
2729
|
| `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
|
|
2730
2730
|
|
|
2731
2731
|
At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
|
|
2732
|
-
|
|
2733
|
-
|
|
2732
|
+
carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
|
|
2733
|
+
composing the same claims and references twice produces the same pack. `createdAt` records when the
|
|
2734
|
+
pack was written and is deliberately outside the digest — it is the one field a restamp can change
|
|
2735
|
+
without breaking validation. Whether a referenced source is still resolvable is reported by
|
|
2736
|
+
`evidence validate`, not stored in the pack.
|
|
2734
2737
|
|
|
2735
2738
|
#### `evidence validate`
|
|
2736
2739
|
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ All notable changes to dbcli are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.0.0] - 2026-08-16 - Evidence that could not reproduce itself, and a hash that hid nothing
|
|
9
|
+
|
|
10
|
+
The evidence subsystem shipped in v1.53.0 and, until this week, nobody had composed a pack outside its own tests. The first real use — a `verify safe-backfill --after-write` against a live PostgreSQL — came back `not_verified` on data that was correct, and the audit that followed found three more defects of the same kind: an evidence pack whose digest covered a random UUID, so the same claims never produced the same pack twice; a receipt "fingerprint" that was an unsalted SHA-256 over eight possible values; and a blacklist comparison with no identifier boundaries, so a protected column named `id` refused any claim containing the word "identifier". Fixing them changes both published formats, which is what makes this a major release: **packs written by 2.x will fail validation under 3.0.0, and `observation.fingerprint` no longer exists.** The reversal that authorized the repairs — known defects get fixed whether or not anyone is using the code — is recorded in `docs/adr/0012-known-defects-get-fixed-whether-or-not-anyone-is-using-the-code.md`, superseding ADR 0011.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **BREAKING: an evidence pack's digest now covers only its content, so equivalent input produces the same pack.** The digest was taken over the whole pack including `id`, which was `evp_${randomUUID()}`, and a millisecond `createdAt` — so composing the same claims twice yielded two unrelated digests and cross-run comparison was not merely hard but undefined. The digest now covers `version` / `subject` / `claims`; `id` is derived from its first 32 characters, so equivalent input yields an identical pack down to the identifier, and `parse` checks that the two agree. `createdAt` sits outside the digest and is documented, in the type and in `reference.md`, as the one field that can be restamped without breaking verification — leaving that unsaid would be selling tamper-evidence that isn't. `canonicalizeWithoutDigest` was `JSON.stringify`, so "canonical" rested on the build and parse paths hand-maintaining the same key insertion order; it is now a real canonicalization that sorts keys recursively. The format is changed in place with no v2 fallback and no compatibility layer: existing packs fail validation, which is correct, because their digests were computed under rules that no longer hold (#116).
|
|
15
|
+
|
|
16
|
+
- **BREAKING: a receipt states its observation instead of hashing it.** `observation.fingerprint` was an unsalted SHA-256 over a preimage space of eight values for `verify` (four `VerificationStatus` × an `artifactPersisted` boolean) and 2^(n+1) for an `assert` with n checks, in a fixed public serialization — a dictionary attack measured in milliseconds. Everything it covered already appeared in plaintext in `outcome`, so the only thing it protected was the per-check pass bit pattern, and the only reader it stopped was an honest one; being deterministic, it also let receipts be grouped by result across files, which is the property the old test suite was asserting. `verify` now records `{ kind: 'verify-outcome', status }` and `assert` records `{ kind: 'assert-verdict', checksPassed, checksTotal }` — counts, not positions, so *which* check failed still does not leave the receipt, and less leaks than before, since anyone willing to invert the old hash recovered the full bit pattern. `parseObservation` validates the field shape per operation and rejects `checksPassed > checksTotal` (#118).
|
|
17
|
+
|
|
18
|
+
- **`evidence` no longer means three incompatible things.** `VerificationStatus` (`verified` / `not_verified` / `indeterminate` / `blocked`), `EvidenceItem.status` (`ok` / `no-data` / `skipped` / `error` / `timeout`) and `WorkloadEvidence.state` (`available` / `absent` / `invalid` / `unavailable`) are a verdict on a subject, whether a diagnostic ran, and whether a source file is usable — three vocabularies that cannot be mapped onto each other, all filed under one word, all string unions the type system cannot keep apart. `EvidenceItem` is now `ReportFinding` and `WorkloadEvidence` is `WorkloadSource` (with `WorkloadEvidenceState`, `LoadWorkloadEvidenceOptions`, `loadWorkloadEvidence` and the command layer's `loadObservedWorkloadEvidence` renamed to match). The values are unchanged and `VerificationStatus` is untouched, being part of a published JSON contract. `CONTEXT.md` gains an "Outcome vocabularies" section stating that they do not map — but the rename is what stops the mistake at the call site, since nobody reads `CONTEXT.md` while writing a comparison (#114).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`scripts/check-plan-acceptance.ts` makes an acceptance criterion say whether anything proved it.** The 2026-08-08 backlog's eight tickets shipped with v1.53.0 and still read `Status: Proposed`, and two of their criteria described behavior the code structurally could not produce. Nothing caught either, because plan documents are prose and prose drifts silently. Every numbered acceptance criterion under `docs/plans/*.md` must now end in `— covered by:` (naming a test file that exists), `— unverified:` (admitting nothing proves it), or `— known deviation:` (deliberately unmet, with a reason). The gate forces disclosure, not coverage — marking all thirty criteria `unverified` would pass — because a gate demanding real tests gets nagged into a rubber stamp, while the count stays visible in review, and eighteen of thirty unverified is its own pressure. The one substantive check is that a cited test file exists, that being the half that is cheap to check and the failure mode the gate exists to stop; prose criteria with no numbered list count as a violation, so reformatting cannot route around it. `docs/plans/done/` is not scanned, since rewriting closed plans to suit a later convention destroys the record this protects. `PLAN_ACCEPTANCE_EXEMPTIONS` is a ratchet shaped after `check-core-no-stdout.ts`: it may only shrink, and a contract test fails when an entry stops being needed (#113).
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
|
|
26
|
+
- **The `coverage` field is gone from the evidence pack rather than made writable.** Both writers hardcoded an empty gap list and the parser rejected a non-empty one, so the ticket's promise that an expired reference produces a coverage gap could never fire. A pack is immutable and a reference expires after composition, so the value could not be written back even in principle; `evidence validate` already reports staleness, and that is where a reader can act on it. A field that can never change says nothing (#116).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **`value == count` was never true on PostgreSQL, so `verify --after-write` could not report `verified` there.** `firstScalar` returned the driver's value as-is and `compare` used `===`, but pg returns `bigint` / `numeric` / `int8` as strings, so `"0" === 0` was permanently false — and a read-back after a write is almost always a count. The asymmetry is what made it hard to see: `>` and `<` went through JS coercion and passed, so on the same column `value > 5` worked while `value == 6` did not, with the output cheerfully printing `expected: "value == 6"`, `actual: "6"`, `pass: false`. Found by dogfooding a real backfill on PostgreSQL 16, where six rows were correctly updated and the assertion still said no (#115).
|
|
31
|
+
|
|
32
|
+
- **Blacklist matching in evidence content is bounded to whole identifiers, and its refusal says where.** Blocked terms were compared with `includes()` — no boundary, no minimum length — so a protected column named `id` refused any claim containing "identifier", "considered" or "valid", and the message was one line reading `evidence content contains a blocked identifier`, naming neither the field nor what it hit. A term now matches only when neither neighbour is a letter, digit or underscore, so `id` still hits `orders.id` and a bare `id` but not `identifier`, and regex metacharacters in a term are escaped, so `a.c` no longer matches `abc`. The keys of `blacklist.columns` — table names — were never collected into the term list at all, a silent hole in the other direction, and are now included. The error names the offending field (`subject.kind`, `claim 2 text`) and still does not name the term, since printing a protected identifier into a message the author may paste elsewhere is the thing the blacklist exists to prevent; claims are located by ordinal because the id may itself be the blocked string. Two limits are documented rather than papered over: `secret_customer` written as "secret customer" still gets through, which identifier matching against free prose cannot honestly promise to catch, and reference fields (audit `command`, receipt `path`) are not checked at all yet (#117).
|
|
33
|
+
|
|
34
|
+
- **The Elasticsearch integration suite had never asserted anything, on any machine.** It reported "container not running on port 9201" against a healthy container answering `200`: Elasticsearch's `GET /` carries no CORS headers, happy-dom's `fetch` drops a header-less response under the same-origin policy, `beforeAll` swallowed the error and every test returned early. It is the only adapter over HTTP, so it was the only casualty. Removing the global preload was tried and reverted — `tests/integration/ui-render-smoke.test.tsx` depends on it, and a per-file import leaks across the files Bun runs in one process, making the outcome depend on file order. `setup-happy-dom.ts` now saves the runtime `fetch` before `GlobalRegistrator.register()` and restores it after, exporting `runtimeFetch` so a test can pin the contract. Only `fetch` is restored: happy-dom's `window` / `document` / `Element` have no runtime counterpart to shadow, and no adapter touches `Request` or `XMLHttpRequest` (#109, #110).
|
|
35
|
+
|
|
36
|
+
- **A negative test asserting that extra fields are rejected no longer breaks `typecheck:tests`.** The object deliberately carrying a field the validator must refuse was written against a type that does not admit it, which `bun test` never sees and CI's `tsc` step fails on across all ten matrix jobs (#118).
|
|
37
|
+
|
|
38
|
+
### Documentation
|
|
39
|
+
|
|
40
|
+
- **ADR 0005's `deferred` described the feature, not the decision.** Of ten ADRs it was the only one not `accepted`, so every inventory picked it back up as an open question — while its content is a settled fail-closed policy with a reopen checklist and a falsification condition already attached. What is deferred is SQD-05 / SQD-06. It is now `accepted`, with a paragraph separating "the policy is settled" from "provider generation is authorized", the latter having not happened — without it, a reader seeing `remain deferred` in the title over an `accepted` status would plausibly "fix" the status (#111).
|
|
41
|
+
|
|
42
|
+
- **The 2026-08-08 evidence backlog now matches what shipped.** Eight tickets read `Status: Proposed` under a spec header saying no implementation was authorized, a week after all of it went out in v1.53.0. Checking all thirty acceptance criteria against the test files found eleven genuinely asserted, eighteen unproven — mostly asserted halfway — and one structurally impossible. Tickets are marked Delivered with their known deviations and unverified criteria listed per line, and `CONTEXT.md` gains `Known deviation` (deliberately unmet) and `Unverified` (nobody proved it) as distinct terms, because merging them dilutes the first into "everything imperfect" (#112).
|
|
43
|
+
|
|
8
44
|
## [2.1.0] - 2026-08-16 - The gate asked the wrong question, and one route never reached it
|
|
9
45
|
|
|
10
46
|
2.0.0 put a two-tier gate in front of raw SQL. This release is what measuring that gate against real servers found: one entire route into the database bypassed it, its qualification criterion asked whether a `WHERE` existed rather than whether it narrowed anything, and its notion of "what kind of statement is this" was the leading keyword — so a full-table delete wearing an `INSERT` or `WITH` in front of it was treated as routine. Each of the three is a way a statement that empties a table reached the database unattended, and each is closed here. The measurement the gate's own ADR bets on is now a command rather than a `jq` invocation somebody has to remember.
|
package/assets/reference.md
CHANGED
|
@@ -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
|
-
> **
|
|
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" \
|
|
@@ -2721,7 +2721,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2721
2721
|
|
|
2722
2722
|
| Flag | Purpose | Default |
|
|
2723
2723
|
|---|---|---|
|
|
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. | — |
|
|
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. 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
2725
|
| `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
|
|
2726
2726
|
| `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
|
|
2727
2727
|
| `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
|
|
@@ -2729,8 +2729,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2729
2729
|
| `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
|
|
2730
2730
|
|
|
2731
2731
|
At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
|
|
2732
|
-
|
|
2733
|
-
|
|
2732
|
+
carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
|
|
2733
|
+
composing the same claims and references twice produces the same pack. `createdAt` records when the
|
|
2734
|
+
pack was written and is deliberately outside the digest — it is the one field a restamp can change
|
|
2735
|
+
without breaking validation. Whether a referenced source is still resolvable is reported by
|
|
2736
|
+
`evidence validate`, not stored in the pack.
|
|
2734
2737
|
|
|
2735
2738
|
#### `evidence validate`
|
|
2736
2739
|
|
package/dist/cli-runtime.mjs
CHANGED
|
@@ -51,7 +51,7 @@ var package_default;
|
|
|
51
51
|
var init_package = __esm(() => {
|
|
52
52
|
package_default = {
|
|
53
53
|
name: "@carllee1983/dbcli",
|
|
54
|
-
version: "
|
|
54
|
+
version: "3.0.0",
|
|
55
55
|
description: "Database CLI for AI agents",
|
|
56
56
|
type: "module",
|
|
57
57
|
publishConfig: {
|
|
@@ -133,6 +133,7 @@ var init_package = __esm(() => {
|
|
|
133
133
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
134
134
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
135
135
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
136
|
+
"plan:check": "bun run scripts/check-plan-acceptance.ts",
|
|
136
137
|
typecheck: "tsc --noEmit --pretty false",
|
|
137
138
|
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
138
139
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
@@ -41525,20 +41526,39 @@ var init_grammar = __esm(() => {
|
|
|
41525
41526
|
});
|
|
41526
41527
|
|
|
41527
41528
|
// src/core/assert/evaluator.ts
|
|
41529
|
+
function numeric(value) {
|
|
41530
|
+
if (typeof value === "number")
|
|
41531
|
+
return Number.isNaN(value) ? null : value;
|
|
41532
|
+
const trimmed = value.trim();
|
|
41533
|
+
if (trimmed === "")
|
|
41534
|
+
return null;
|
|
41535
|
+
const parsed = Number(trimmed);
|
|
41536
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
41537
|
+
}
|
|
41528
41538
|
function compare(a, op, b) {
|
|
41539
|
+
let left = a;
|
|
41540
|
+
let right = b;
|
|
41541
|
+
if (typeof a !== typeof b) {
|
|
41542
|
+
const na = numeric(a);
|
|
41543
|
+
const nb = numeric(b);
|
|
41544
|
+
if (na !== null && nb !== null) {
|
|
41545
|
+
left = na;
|
|
41546
|
+
right = nb;
|
|
41547
|
+
}
|
|
41548
|
+
}
|
|
41529
41549
|
switch (op) {
|
|
41530
41550
|
case ">":
|
|
41531
|
-
return
|
|
41551
|
+
return left > right;
|
|
41532
41552
|
case ">=":
|
|
41533
|
-
return
|
|
41553
|
+
return left >= right;
|
|
41534
41554
|
case "<":
|
|
41535
|
-
return
|
|
41555
|
+
return left < right;
|
|
41536
41556
|
case "<=":
|
|
41537
|
-
return
|
|
41557
|
+
return left <= right;
|
|
41538
41558
|
case "==":
|
|
41539
|
-
return
|
|
41559
|
+
return left === right;
|
|
41540
41560
|
case "!=":
|
|
41541
|
-
return
|
|
41561
|
+
return left !== right;
|
|
41542
41562
|
}
|
|
41543
41563
|
}
|
|
41544
41564
|
function firstScalar(result) {
|
|
@@ -41591,7 +41611,10 @@ function evaluateExpect(node, result) {
|
|
|
41591
41611
|
};
|
|
41592
41612
|
}
|
|
41593
41613
|
if (pred.type === "between") {
|
|
41594
|
-
const bad2 = nonNull.filter((v) =>
|
|
41614
|
+
const bad2 = nonNull.filter((v) => {
|
|
41615
|
+
const n = numeric(v);
|
|
41616
|
+
return n === null || n < pred.low || n > pred.high;
|
|
41617
|
+
});
|
|
41595
41618
|
return {
|
|
41596
41619
|
name: `col:${column} between ${pred.low} and ${pred.high}`,
|
|
41597
41620
|
expected: "0 out of range",
|
|
@@ -41622,7 +41645,7 @@ function compareVs(a, b, mode) {
|
|
|
41622
41645
|
name: "vs:value",
|
|
41623
41646
|
expected: String(bv),
|
|
41624
41647
|
actual: String(av),
|
|
41625
|
-
pass: av !== null && bv !== null && av
|
|
41648
|
+
pass: av !== null && bv !== null && compare(av, "==", bv)
|
|
41626
41649
|
};
|
|
41627
41650
|
}
|
|
41628
41651
|
var AssertShapeError;
|
|
@@ -41695,7 +41718,7 @@ function canonicalizeEvidenceReceiptCommand(command, operation = "assert") {
|
|
|
41695
41718
|
function sha2563(value) {
|
|
41696
41719
|
return `sha256:${createHash5("sha256").update(value).digest("hex")}`;
|
|
41697
41720
|
}
|
|
41698
|
-
function
|
|
41721
|
+
function verdictObservation(verdict2) {
|
|
41699
41722
|
if (!record3(verdict2) || typeof verdict2.pass !== "boolean" || !Array.isArray(verdict2.checks) || verdict2.checks.length > 1000) {
|
|
41700
41723
|
throw new EvidenceReceiptValidationError("verdict must contain bounded pass bits");
|
|
41701
41724
|
}
|
|
@@ -41705,26 +41728,30 @@ function verdictFingerprint(verdict2) {
|
|
|
41705
41728
|
}
|
|
41706
41729
|
return check.pass;
|
|
41707
41730
|
});
|
|
41708
|
-
return
|
|
41731
|
+
return {
|
|
41732
|
+
kind: "assert-verdict",
|
|
41733
|
+
checksPassed: bits.filter(Boolean).length,
|
|
41734
|
+
checksTotal: bits.length
|
|
41735
|
+
};
|
|
41709
41736
|
}
|
|
41710
|
-
function
|
|
41737
|
+
function verificationObservation(status2, artifactPersisted) {
|
|
41711
41738
|
if (!isVerificationStatus(status2)) {
|
|
41712
41739
|
throw new EvidenceReceiptValidationError("verificationStatus must be a verification status");
|
|
41713
41740
|
}
|
|
41714
41741
|
if (typeof artifactPersisted !== "boolean") {
|
|
41715
41742
|
throw new EvidenceReceiptValidationError("verificationArtifactPersisted must be a boolean");
|
|
41716
41743
|
}
|
|
41717
|
-
return
|
|
41744
|
+
return { kind: "verify-outcome", status: status2 };
|
|
41718
41745
|
}
|
|
41719
41746
|
function buildEvidenceReceipt(input, options = {}) {
|
|
41720
41747
|
const operation = input.operation === "verify" ? "verify" : "assert";
|
|
41721
41748
|
const command = canonicalizeEvidenceReceiptCommand(input.command, operation);
|
|
41722
41749
|
const context = parseContext2(input.context);
|
|
41723
|
-
const {
|
|
41724
|
-
|
|
41750
|
+
const { observation, outcome } = input.operation === "verify" ? {
|
|
41751
|
+
observation: verificationObservation(input.verificationStatus, input.verificationArtifactPersisted),
|
|
41725
41752
|
outcome: input.verificationStatus === "verified" && input.verificationArtifactPersisted ? "succeeded" : "failed"
|
|
41726
41753
|
} : {
|
|
41727
|
-
|
|
41754
|
+
observation: verdictObservation(input.verdict),
|
|
41728
41755
|
outcome: input.verdict.pass ? "succeeded" : "failed"
|
|
41729
41756
|
};
|
|
41730
41757
|
const receipt = {
|
|
@@ -41741,10 +41768,7 @@ function buildEvidenceReceipt(input, options = {}) {
|
|
|
41741
41768
|
verificationArtifactRef: input.verificationArtifactRef === undefined ? null : nullableId(input.verificationArtifactRef, "verificationArtifactRef")
|
|
41742
41769
|
},
|
|
41743
41770
|
replay: { status: "context-required" },
|
|
41744
|
-
observation
|
|
41745
|
-
kind: operation === "assert" ? "assert-verdict" : "verify-outcome",
|
|
41746
|
-
fingerprint: observationFingerprint
|
|
41747
|
-
}
|
|
41771
|
+
observation
|
|
41748
41772
|
};
|
|
41749
41773
|
return receipt;
|
|
41750
41774
|
}
|
|
@@ -41789,9 +41813,7 @@ function parseEvidenceReceipt(raw) {
|
|
|
41789
41813
|
throw new EvidenceReceiptValidationError("command provenance hash mismatch");
|
|
41790
41814
|
if (!record3(raw.replay) || raw.replay.status !== "context-required" || Object.keys(raw.replay).length !== 1)
|
|
41791
41815
|
throw new EvidenceReceiptValidationError("replay must be context-required");
|
|
41792
|
-
|
|
41793
|
-
throw new EvidenceReceiptValidationError("observation must match receipt operation");
|
|
41794
|
-
const observationKind = operation === "assert" ? "assert-verdict" : "verify-outcome";
|
|
41816
|
+
const observation = parseObservation(raw.observation, operation);
|
|
41795
41817
|
return {
|
|
41796
41818
|
version: EVIDENCE_RECEIPT_VERSION,
|
|
41797
41819
|
id: safeId(raw.id, "id"),
|
|
@@ -41806,10 +41828,26 @@ function parseEvidenceReceipt(raw) {
|
|
|
41806
41828
|
verificationArtifactRef: nullableId(raw.provenance.verificationArtifactRef, "provenance.verificationArtifactRef")
|
|
41807
41829
|
},
|
|
41808
41830
|
replay: { status: "context-required" },
|
|
41809
|
-
observation
|
|
41810
|
-
|
|
41811
|
-
|
|
41812
|
-
|
|
41831
|
+
observation
|
|
41832
|
+
};
|
|
41833
|
+
}
|
|
41834
|
+
function parseObservation(raw, operation) {
|
|
41835
|
+
if (!record3(raw))
|
|
41836
|
+
throw new EvidenceReceiptValidationError("observation must be an object");
|
|
41837
|
+
if (operation === "verify") {
|
|
41838
|
+
exact(raw, ["kind", "status"], "observation");
|
|
41839
|
+
if (raw.kind !== "verify-outcome" || !isVerificationStatus(raw.status))
|
|
41840
|
+
throw new EvidenceReceiptValidationError("observation must match receipt operation");
|
|
41841
|
+
return { kind: "verify-outcome", status: raw.status };
|
|
41842
|
+
}
|
|
41843
|
+
exact(raw, ["kind", "checksPassed", "checksTotal"], "observation");
|
|
41844
|
+
const { kind, checksPassed, checksTotal } = raw;
|
|
41845
|
+
if (kind !== "assert-verdict" || !Number.isSafeInteger(checksPassed) || !Number.isSafeInteger(checksTotal) || checksPassed < 0 || checksTotal < checksPassed)
|
|
41846
|
+
throw new EvidenceReceiptValidationError("observation must match receipt operation");
|
|
41847
|
+
return {
|
|
41848
|
+
kind: "assert-verdict",
|
|
41849
|
+
checksPassed,
|
|
41850
|
+
checksTotal
|
|
41813
41851
|
};
|
|
41814
41852
|
}
|
|
41815
41853
|
function inside(root, target) {
|
|
@@ -49868,11 +49906,19 @@ function assertVerificationSubjects(subject, claims) {
|
|
|
49868
49906
|
throw new EvidencePackValidationError("verification evidence must match the claims subject kind");
|
|
49869
49907
|
}
|
|
49870
49908
|
}
|
|
49871
|
-
function
|
|
49872
|
-
|
|
49909
|
+
function canonicalize(value) {
|
|
49910
|
+
if (value === null || typeof value !== "object")
|
|
49911
|
+
return JSON.stringify(value) ?? "null";
|
|
49912
|
+
if (Array.isArray(value))
|
|
49913
|
+
return `[${value.map(canonicalize).join(",")}]`;
|
|
49914
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, v]) => `${JSON.stringify(key)}:${canonicalize(v)}`);
|
|
49915
|
+
return `{${entries.join(",")}}`;
|
|
49873
49916
|
}
|
|
49874
|
-
function digest(
|
|
49875
|
-
return createHash8("sha256").update(
|
|
49917
|
+
function digest(content) {
|
|
49918
|
+
return createHash8("sha256").update(canonicalize(content)).digest("hex");
|
|
49919
|
+
}
|
|
49920
|
+
function packId(contentDigest) {
|
|
49921
|
+
return `evp_${contentDigest.slice(0, 32)}`;
|
|
49876
49922
|
}
|
|
49877
49923
|
function parseEvidenceClaimsInput(raw) {
|
|
49878
49924
|
if (!isRecord4(raw))
|
|
@@ -49907,49 +49953,52 @@ function buildEvidencePack(input, references, options = {}) {
|
|
|
49907
49953
|
if (evidence2.some((reference) => reference.kind === "verification-artifact" && reference.subjectKind !== normalized2.subject.kind)) {
|
|
49908
49954
|
throw new EvidencePackValidationError("verification evidence must match the claims subject kind");
|
|
49909
49955
|
}
|
|
49910
|
-
const
|
|
49956
|
+
const content = {
|
|
49911
49957
|
version: EVIDENCE_PACK_VERSION,
|
|
49912
|
-
id: id(options.idFactory?.() ?? `evp_${randomUUID6()}`, "pack.id"),
|
|
49913
|
-
createdAt: (options.now?.() ?? new Date).toISOString(),
|
|
49914
49958
|
subject: normalized2.subject,
|
|
49915
|
-
claims: normalized2.claims.map((claim) => ({ ...claim, evidence: evidence2 }))
|
|
49916
|
-
|
|
49959
|
+
claims: normalized2.claims.map((claim) => ({ ...claim, evidence: evidence2 }))
|
|
49960
|
+
};
|
|
49961
|
+
const contentDigest = digest(content);
|
|
49962
|
+
return {
|
|
49963
|
+
...content,
|
|
49964
|
+
id: packId(contentDigest),
|
|
49965
|
+
createdAt: (options.now?.() ?? new Date).toISOString(),
|
|
49966
|
+
integrity: { algorithm: "sha256", digest: contentDigest }
|
|
49917
49967
|
};
|
|
49918
|
-
return { ...base, integrity: { algorithm: "sha256", digest: digest(base) } };
|
|
49919
49968
|
}
|
|
49920
49969
|
function parseEvidencePack(raw) {
|
|
49921
49970
|
if (!isRecord4(raw))
|
|
49922
49971
|
throw new EvidencePackValidationError("evidence pack must be an object");
|
|
49923
|
-
requireExactKeys(raw, ["version", "id", "createdAt", "subject", "claims", "
|
|
49972
|
+
requireExactKeys(raw, ["version", "id", "createdAt", "subject", "claims", "integrity"], "evidence pack");
|
|
49924
49973
|
if (raw.version !== EVIDENCE_PACK_VERSION) {
|
|
49925
49974
|
throw new EvidencePackValidationError(`evidence pack version must be ${EVIDENCE_PACK_VERSION}`);
|
|
49926
49975
|
}
|
|
49927
|
-
if (!isRecord4(raw.coverage))
|
|
49928
|
-
throw new EvidencePackValidationError("coverage must be an object");
|
|
49929
|
-
requireExactKeys(raw.coverage, ["completeForDeclaredEvidence", "gaps"], "coverage");
|
|
49930
|
-
if (raw.coverage.completeForDeclaredEvidence !== true || !Array.isArray(raw.coverage.gaps) || raw.coverage.gaps.length !== 0) {
|
|
49931
|
-
throw new EvidencePackValidationError("v1 coverage must be complete for declared evidence with no gaps");
|
|
49932
|
-
}
|
|
49933
49976
|
if (!isRecord4(raw.integrity))
|
|
49934
49977
|
throw new EvidencePackValidationError("integrity must be an object");
|
|
49935
49978
|
requireExactKeys(raw.integrity, ["algorithm", "digest"], "integrity");
|
|
49936
49979
|
if (raw.integrity.algorithm !== "sha256")
|
|
49937
49980
|
throw new EvidencePackValidationError("integrity.algorithm must be sha256");
|
|
49938
|
-
const
|
|
49981
|
+
const content = {
|
|
49939
49982
|
version: EVIDENCE_PACK_VERSION,
|
|
49940
|
-
id: id(raw.id, "pack.id"),
|
|
49941
|
-
createdAt: iso(raw.createdAt, "pack.createdAt"),
|
|
49942
49983
|
subject: parseSubject(raw.subject),
|
|
49943
|
-
claims: parseClaims(raw.claims, true)
|
|
49944
|
-
coverage: { completeForDeclaredEvidence: true, gaps: [] }
|
|
49984
|
+
claims: parseClaims(raw.claims, true)
|
|
49945
49985
|
};
|
|
49946
|
-
assertVerificationSubjects(
|
|
49986
|
+
assertVerificationSubjects(content.subject, content.claims);
|
|
49947
49987
|
const actualDigest = text4(raw.integrity.digest, "integrity.digest", 128);
|
|
49948
49988
|
if (!/^[a-f0-9]{64}$/.test(actualDigest))
|
|
49949
49989
|
throw new EvidencePackValidationError("integrity.digest must be sha256 hex");
|
|
49950
|
-
|
|
49990
|
+
const expectedDigest = digest(content);
|
|
49991
|
+
if (actualDigest !== expectedDigest)
|
|
49951
49992
|
throw new EvidencePackValidationError("evidence pack digest mismatch");
|
|
49952
|
-
|
|
49993
|
+
const packIdentity = id(raw.id, "pack.id");
|
|
49994
|
+
if (packIdentity !== packId(expectedDigest))
|
|
49995
|
+
throw new EvidencePackValidationError("evidence pack id does not match its content digest");
|
|
49996
|
+
return {
|
|
49997
|
+
...content,
|
|
49998
|
+
id: packIdentity,
|
|
49999
|
+
createdAt: iso(raw.createdAt, "pack.createdAt"),
|
|
50000
|
+
integrity: { algorithm: "sha256", digest: actualDigest }
|
|
50001
|
+
};
|
|
49953
50002
|
}
|
|
49954
50003
|
function isInside(root, target) {
|
|
49955
50004
|
const relativePath = relative5(root, target);
|
|
@@ -50072,7 +50121,9 @@ var init_evidence_pack = __esm(() => {
|
|
|
50072
50121
|
// src/commands/evidence.ts
|
|
50073
50122
|
var exports_evidence = {};
|
|
50074
50123
|
__export(exports_evidence, {
|
|
50075
|
-
evidenceCommand: () => evidenceCommand
|
|
50124
|
+
evidenceCommand: () => evidenceCommand,
|
|
50125
|
+
containsBlockedTerm: () => containsBlockedTerm,
|
|
50126
|
+
blockedTerms: () => blockedTerms
|
|
50076
50127
|
});
|
|
50077
50128
|
import { join as join42, relative as relative6, resolve as resolve12, sep as sep8 } from "path";
|
|
50078
50129
|
import { realpath as realpath3 } from "fs/promises";
|
|
@@ -50095,24 +50146,34 @@ async function readClaimsInput(filePath) {
|
|
|
50095
50146
|
function blockedTerms(config) {
|
|
50096
50147
|
return [
|
|
50097
50148
|
...config.blacklist?.tables ?? [],
|
|
50149
|
+
...Object.keys(config.blacklist?.columns ?? {}),
|
|
50098
50150
|
...Object.values(config.blacklist?.columns ?? {}).flat()
|
|
50099
50151
|
].map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
50100
50152
|
}
|
|
50101
|
-
function
|
|
50153
|
+
function containsBlockedTerm(value, terms) {
|
|
50154
|
+
const normalized2 = value.toLowerCase();
|
|
50155
|
+
return terms.some((term) => {
|
|
50156
|
+
const trimmed = term.trim().toLowerCase();
|
|
50157
|
+
if (trimmed.length === 0)
|
|
50158
|
+
return false;
|
|
50159
|
+
const escaped = trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50160
|
+
return new RegExp(`(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])`).test(normalized2);
|
|
50161
|
+
});
|
|
50162
|
+
}
|
|
50163
|
+
function assertNoBlockedText(value, terms, field) {
|
|
50102
50164
|
if (!value)
|
|
50103
50165
|
return;
|
|
50104
|
-
|
|
50105
|
-
|
|
50106
|
-
throw new EvidencePackValidationError("evidence content contains a blocked identifier");
|
|
50166
|
+
if (containsBlockedTerm(value, terms)) {
|
|
50167
|
+
throw new EvidencePackValidationError(`${field} contains a blocked identifier`);
|
|
50107
50168
|
}
|
|
50108
50169
|
}
|
|
50109
50170
|
function assertClaimsSafe(raw, terms) {
|
|
50110
50171
|
const input = parseEvidenceClaimsInput(raw);
|
|
50111
|
-
assertNoBlockedText(input.subject.kind, terms);
|
|
50112
|
-
assertNoBlockedText(input.subject.name, terms);
|
|
50113
|
-
for (const claim of input.claims) {
|
|
50114
|
-
assertNoBlockedText(claim.id, terms);
|
|
50115
|
-
assertNoBlockedText(claim.text, terms);
|
|
50172
|
+
assertNoBlockedText(input.subject.kind, terms, "subject.kind");
|
|
50173
|
+
assertNoBlockedText(input.subject.name, terms, "subject.name");
|
|
50174
|
+
for (const [index, claim] of input.claims.entries()) {
|
|
50175
|
+
assertNoBlockedText(claim.id, terms, `claim ${index + 1} id`);
|
|
50176
|
+
assertNoBlockedText(claim.text, terms, `claim ${index + 1} text`);
|
|
50116
50177
|
}
|
|
50117
50178
|
}
|
|
50118
50179
|
function sameReference(left, right) {
|
|
@@ -51302,7 +51363,7 @@ var init_change_set = __esm(() => {
|
|
|
51302
51363
|
import { createReadStream } from "fs";
|
|
51303
51364
|
import { lstat as lstat7 } from "fs/promises";
|
|
51304
51365
|
import { createInterface as createInterface3 } from "readline";
|
|
51305
|
-
async function
|
|
51366
|
+
async function loadWorkloadSource(options) {
|
|
51306
51367
|
const now = options.now ?? new Date;
|
|
51307
51368
|
let info;
|
|
51308
51369
|
try {
|
|
@@ -51683,7 +51744,7 @@ function semanticSchema(schema, blocked) {
|
|
|
51683
51744
|
}
|
|
51684
51745
|
return result;
|
|
51685
51746
|
}
|
|
51686
|
-
async function
|
|
51747
|
+
async function loadObservedWorkloadSource(path4, workspaceRoot, blocked) {
|
|
51687
51748
|
if (path4 === undefined) {
|
|
51688
51749
|
return {
|
|
51689
51750
|
state: "absent",
|
|
@@ -51693,7 +51754,7 @@ async function loadObservedWorkloadEvidence(path4, workspaceRoot, blocked) {
|
|
|
51693
51754
|
issues: []
|
|
51694
51755
|
};
|
|
51695
51756
|
}
|
|
51696
|
-
return
|
|
51757
|
+
return loadWorkloadSource({ path: resolve14(workspaceRoot, path4), blockedIdentifiers: blocked });
|
|
51697
51758
|
}
|
|
51698
51759
|
function shouldFail(report, failOn) {
|
|
51699
51760
|
if (failOn === "never")
|
|
@@ -51831,7 +51892,7 @@ var init_impact2 = __esm(() => {
|
|
|
51831
51892
|
const contracts = await loadContractEvidence(workspaceRoot, semantic, baseline, savedQueries, blockedIdentifiers);
|
|
51832
51893
|
const dataAccess = await loadDataAccessEvidence(workspaceRoot, semantic, baseline, savedQueries, blockedIdentifiers);
|
|
51833
51894
|
const verifications = await loadVerificationEvidence(workspaceRoot, blockedIdentifiers);
|
|
51834
|
-
const observedWorkload = await
|
|
51895
|
+
const observedWorkload = await loadObservedWorkloadSource(options.events, workspaceRoot, blockedIdentifiers);
|
|
51835
51896
|
const report = assessImpact({
|
|
51836
51897
|
changes,
|
|
51837
51898
|
semantic,
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@carllee1983/dbcli",
|
|
6
|
-
version: "
|
|
6
|
+
version: "3.0.0",
|
|
7
7
|
description: "Database CLI for AI agents",
|
|
8
8
|
type: "module",
|
|
9
9
|
publishConfig: {
|
|
@@ -85,6 +85,7 @@ var package_default = {
|
|
|
85
85
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
86
86
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
87
87
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
88
|
+
"plan:check": "bun run scripts/check-plan-acceptance.ts",
|
|
88
89
|
typecheck: "tsc --noEmit --pretty false",
|
|
89
90
|
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
90
91
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carllee1983/dbcli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Database CLI for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
83
83
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
84
84
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
85
|
+
"plan:check": "bun run scripts/check-plan-acceptance.ts",
|
|
85
86
|
"typecheck": "tsc --noEmit --pretty false",
|
|
86
87
|
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
87
88
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
@@ -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
|
-
> **
|
|
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" \
|
|
@@ -2721,7 +2721,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2721
2721
|
|
|
2722
2722
|
| Flag | Purpose | Default |
|
|
2723
2723
|
|---|---|---|
|
|
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. | — |
|
|
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. 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
2725
|
| `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
|
|
2726
2726
|
| `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
|
|
2727
2727
|
| `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
|
|
@@ -2729,8 +2729,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2729
2729
|
| `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
|
|
2730
2730
|
|
|
2731
2731
|
At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
|
|
2732
|
-
|
|
2733
|
-
|
|
2732
|
+
carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
|
|
2733
|
+
composing the same claims and references twice produces the same pack. `createdAt` records when the
|
|
2734
|
+
pack was written and is deliberately outside the digest — it is the one field a restamp can change
|
|
2735
|
+
without breaking validation. Whether a referenced source is still resolvable is reported by
|
|
2736
|
+
`evidence validate`, not stored in the pack.
|
|
2734
2737
|
|
|
2735
2738
|
#### `evidence validate`
|
|
2736
2739
|
|
|
@@ -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
|
-
> **
|
|
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" \
|
|
@@ -2721,7 +2721,7 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2721
2721
|
|
|
2722
2722
|
| Flag | Purpose | Default |
|
|
2723
2723
|
|---|---|---|
|
|
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. | — |
|
|
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. 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
2725
|
| `--verification <selector...>` | One or more verification artifact ids, unique prefixes, filenames, or in-bounds paths. | none |
|
|
2726
2726
|
| `--audit <selector...>` | One or more audit ids or unique prefixes (minimum four characters) from the active connection. | none |
|
|
2727
2727
|
| `--receipt <path...>` | Explicit workspace-relative receipt path(s). Receipts contribute safe provenance only; they are not execution approval. | none |
|
|
@@ -2729,8 +2729,11 @@ dbcli evidence compose --claims ./claims.json --verification ver_abcd --audit 1a
|
|
|
2729
2729
|
| `--format <json\|markdown>` | Compose receipt format printed to stdout. | `json` |
|
|
2730
2730
|
|
|
2731
2731
|
At least one `--verification`, `--audit`, or `--receipt` reference is required. The resulting pack
|
|
2732
|
-
|
|
2733
|
-
|
|
2732
|
+
carries a canonical SHA-256 digest over its content, and its `id` is derived from that digest, so
|
|
2733
|
+
composing the same claims and references twice produces the same pack. `createdAt` records when the
|
|
2734
|
+
pack was written and is deliberately outside the digest — it is the one field a restamp can change
|
|
2735
|
+
without breaking validation. Whether a referenced source is still resolvable is reported by
|
|
2736
|
+
`evidence validate`, not stored in the pack.
|
|
2734
2737
|
|
|
2735
2738
|
#### `evidence validate`
|
|
2736
2739
|
|