Skip to content

feat(attestation-type): custom attestation type summaries - #1099

Open
FayeSGW wants to merge 3 commits into
mainfrom
1097-add-cat-summaries
Open

feat(attestation-type): custom attestation type summaries#1099
FayeSGW wants to merge 3 commits into
mainfrom
1097-add-cat-summaries

Conversation

@FayeSGW

@FayeSGW FayeSGW commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Closes #1097

What

Adds --summary-json to kosli create attestation-type, and shows the summary in kosli get attestation-type table output.

The backend accepts an optional summary list at the top level of the create/update endpoint's data_json body — ordered {name, expression} entries whose expressions are jq, evaluated against attestation data. Because it lives in that JSON blob rather than the uploaded schema file, there was no user-facing way to set it.

kosli create attestation-type security-scan \
  --schema scan-schema.json \
  --jq '.critical_count == 0' \
  --summary-json '[{"name":"Critical","expression":".critical_count"},{"name":"Scanner","expression":".scanner.name"}]'
                   Evaluator:
                                 Content Type:  jq
                                 Rules:
                                      .critical_count == 0
                   Summary:
                                 Critical:  .critical_count
                                 Scanner:   .scanner.name

Why this flag shape

The issue floated three options. This takes the JSON-passthrough one, which maps 1:1 to the API.

The repeatable --summary "Name=.expr" alternative was rejected because jq expressions contain both = and ,. Note the existing key=value idiom in this repo (--annotate, --set, --link) uses cobra's StringToStringVar, which would have been wrong here twice over: it CSV-splits values, so an expression like [.a, .b] | map(select(.x == 1)) | length gets mangled, and it returns an unordered map where summary is an ordered list.

Notes for review

Validation. Each entry must have a non-empty name and expression, reported by 1-based index. Without that, a typo such as {"name":"X","expr":".y"} unmarshals silently into an empty expression and ships a broken summary to the API, surfacing far from its cause. DisallowUnknownFields would catch that more directly, but would hard-reject payloads if the backend later adds an optional field to the entry shape — which the issue explicitly reserves the right to do.

omitempty is safe. Verified against a local server that the endpoint replaces rather than merges: creating a type with a summary and re-running without the flag produces a version with summary: null. So omitting the flag clears the summary, and [] vs "omitted" being indistinguishable on the wire loses no expressiveness. Existing invocations produce a byte-identical request.

Second commit came from code review — the summary was write-only in the default view, visible only via --output json. Combined with full-replace semantics, a later create without the flag silently cleared it with nothing in the table to show it had gone.

Testing

Full suite green: 2205 tests, 51 skipped. make lint clean.

Beyond the goldens, I queried the local server after the suite to confirm the data actually lands rather than just being accepted: the comma/== expression stores byte-identical, and summary coexists with both a jq evaluator and an uploaded type_schema.

Types created without a summary have a null summary in the response, which fails the []interface{} assertion and prints nothing — existing golden files pass untouched.

Known gap

No test pins CreateAttestationTypePayload to the OpenAPI contract — it is not in the openapiContract_test.go drift-guard registry, so a server-side rename would leave the suite green while the summary is silently dropped. The new get-with-summary golden partly mitigates this by round-tripping the field through a real server. Happy to add the registry entry here or as a follow-up.

🤖 Generated with Claude Code

FayeSGW and others added 2 commits August 18, 2026 11:24
The backend accepts an optional `summary` list at the top level of the
create/update endpoint's data_json body — ordered {name, expression}
entries whose expressions are jq, evaluated against attestation data.
Because it lives in that JSON blob rather than the uploaded schema file,
there was no user-facing way to set it.

Adds --summary-json, taking the list as JSON so it maps 1:1 to the API.
The repeatable --summary "Name=.expr" alternative was rejected: jq
expressions contain both `=` and `,`, and cobra's StringToStringVar
CSV-splits values and returns an unordered map, where summary is an
ordered list.

Parsing validates that each entry has a non-empty name and expression,
reporting the offending entry by 1-based index. Without that, a typo
such as {"name":"X","expr":".y"} unmarshals silently into an empty
expression and ships a broken summary to the API, surfacing far from
its cause. DisallowUnknownFields would catch that more directly but
would hard-reject payloads if the backend later adds an optional field
to the entry shape, which it has reserved the right to do.

The field uses omitempty, so existing invocations produce a byte-
identical request. Verified against the local server that the endpoint
replaces rather than merges: re-running without the flag clears an
existing summary, so omitempty loses no expressiveness.

Refs #1097

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tput

The summary was write-only in the default view: --summary-json could set
it, but `kosli get attestation-type X` printed type_schema and evaluator
with no summary branch, so the only way to see what had been set was
--output json. Combined with the endpoint's full-replace semantics, a
later create without the flag silently cleared the summary with nothing
in the table output to show it had gone.

Prints entries under a Summary block after Evaluator, one name/expression
pair per line, in the order the server returns them.

Types created without a summary have a null "summary" in the response,
which fails the []interface{} type assertion and prints nothing, so their
output is unchanged — the existing golden files still pass untouched.

Refs #1097

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @FayeSGW's task in 1m 31s —— View job


PR Review: custom attestation type summaries

  • Static analysis (go build, go vet, go mod tidy)
  • Review the diff & changed files
  • Post review feedback

Static checks: go build ./... ✅ · go vet ./... ✅ · go mod tidygo.mod/go.sum clean ✅


This is a well-executed, well-tested change. The flag-shape reasoning in the PR body is sound: StringToStringVar would indeed have mangled jq expressions on both , and the unordered-map problem, so raw JSON passthrough is the right call. Nice work.

What's good

  • parseSummaryJSON error handling is precise. Distinguishing *json.SyntaxError (malformed JSON) from type-mismatch/validation errors gives genuinely useful messages, and 1-based, position-tracking indices are a thoughtful touch. The test matrix ([null], [1,2,3], ["a"], [] trailing, whitespace-only fields, second-entry-is-bad) is comprehensive and pins each branch.
  • omitempty semantics verified against a real server and documented — the note that omitted vs [] is indistinguishable and lossless is exactly the analysis this needs. Confirmed existing invocations stay byte-identical.
  • Second commit (showing the summary in the default table) was the right catch — a write-only field under full-replace semantics is a silent-data-loss trap. Good that review surfaced it.
  • New table code mirrors the existing evaluator block's map[string]interface{} idiom, so it reads as part of the file rather than bolted on.

Minor points (non-blocking)

  1. <nil> rendering on malformed server data (getAttestationType.go:202) — already raised in the prior inline review. Worth noting it's consistent with the neighbouring evaluator code (evaluator["content_type"] has the same behaviour), so leaving it is defensible for consistency; create-side validation guarantees both fields non-empty for anything this CLI writes. Fine to leave or guard — your call.

  2. The OpenAPI drift-guard gap you flagged is a one-lineropenapiContract_test.go:67 takes a single registry entry: {name: "create attestation-type", payload: CreateAttestationTypePayload{}, component: "<ComponentName>"}. Since the whole risk is a server-side rename silently dropping summary, and the fix is this small, I'd lean toward adding it in this PR rather than as a follow-up — the round-trip golden only mitigates it while the local server stays in sync. Only blocker is knowing the exact OpenAPI component name.

Testing

Golden files, unit tests for parseSummaryJSON, and the round-trip get-with-summary golden together give solid coverage. I could not run make test_integration here (requires the local Kosli server + API tokens), but the suite is reported green (2205 passed, 51 skipped) and the goldens look correct.

Overall: clean, focused, mergeable. 👍
· 1097-add-cat-summaries

Comment thread cmd/kosli/getAttestationType.go
Snyk Dependency Test fails on every branch, flagging seven vulnerabilities
in the Go standard library at 1.26.5:

  High    DoS in std/crypto/tls           CVE-2026-56862
  High    DoS in std/net/http             CVE-2026-56858
  High    DoS in std/net/url              CVE-2026-56859
  High    Uncaught Exception in std/net   CVE-2026-56860
  High    Uncontrolled Recursion in std/encoding/asn1
  High    Uncontrolled Recursion in std/encoding/xml  CVE-2026-56853
  Medium  XSS in std/html/template

Snyk derives the stdlib version from the `go` directive, so go.mod was the
only place needing a change — .go-version and the Dockerfile's GO_VERSION
arg both float on 1.26 and pick up the patch release on their own.

Verified locally: `snyk test --policy-path=.snyk` reports 7 issues and 940
vulnerable paths on 1.26.5, and no vulnerable paths on 1.26.6. Full suite
and lint pass on the new toolchain.

Not caused by this branch — the unrelated empty-flag-audit branch fails the
same check. Included here to unblock CI; can be split into its own PR if
preferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@sami-alajrami sami-alajrami left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume expression validation to be a valid JQ is still happening on the API?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: custom attestation summaries

2 participants