Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
795d432
feat(otdf-local): multi-instance test environments (DSPX-3302)
dmihalcik-virtru May 15, 2026
0597e26
feat(otdf-local): self-provision keys + opentdf.yaml at instance init
dmihalcik-virtru Jun 2, 2026
e4b2e69
fix(otdf-local): translate scenario suite to pytest argv per actual s…
dmihalcik-virtru Jun 2, 2026
3250bfa
style(otdf-local): apply ruff format
dmihalcik-virtru Jun 2, 2026
b54aae9
fix(otdf-local): address review feedback — instance-aware up ports, c…
dmihalcik-virtru Jun 9, 2026
02d7fd2
fix(otdf-local): address pyright errors in cli and cli_instance
dmihalcik-virtru Jun 9, 2026
3ba16e3
fix(otdf-local): address coderabbit review feedback
dmihalcik-virtru Jun 9, 2026
0e89a71
fix(otdf-local): restore PKCS12→JKS flow in generate_ca_jks
dmihalcik-virtru Jun 9, 2026
36e3147
docs: add simplification design spec for multi-instance PR
dmihalcik-virtru Jun 9, 2026
a86d59a
refactor(otdf-local): unify Ports.get_kas_port to always use KAS_OFFSETS
dmihalcik-virtru Jun 10, 2026
2fbd977
refactor(otdf-local): add resolve_binary_worktree() and cache load_in…
dmihalcik-virtru Jun 10, 2026
d319600
refactor(otdf-local): delegate KASService._instance_paths() to settin…
dmihalcik-virtru Jun 10, 2026
3a8699c
refactor(otdf-local): delegate PlatformService._instance_dist_paths()…
dmihalcik-virtru Jun 10, 2026
653db21
refactor(otdf-local): delete _resolve_platform_worktree(), inline int…
dmihalcik-virtru Jun 10, 2026
1842283
fixup remove devlocal spec
dmihalcik-virtru Jun 10, 2026
837416f
feat(xtest): --scenario and --instance flags for conftest (DSPX-3302)
dmihalcik-virtru May 15, 2026
e753ba6
feat(.claude): bug-repro plugin for OpenTDF (DSPX-3302)
dmihalcik-virtru May 15, 2026
b9b814a
refactor(.claude): generalize scenario-from-bug-report → scenario-fro…
dmihalcik-virtru May 15, 2026
9a0a7fb
feat(.claude): feature-design skill for cross-repo features (DSPX-3302)
dmihalcik-virtru May 16, 2026
cef6441
fix(.claude): allow Skill tool + correct acli comment list syntax (DS…
dmihalcik-virtru May 16, 2026
d7ceddd
feat(otdf-sdk-mgr): schema dump CLI + xtest/schema canonical JSON Sch…
dmihalcik-virtru May 16, 2026
a36ee42
fixup play nicer with claude code permissions model
dmihalcik-virtru May 22, 2026
844a033
fixup better skill descriptions
dmihalcik-virtru May 22, 2026
0fbed4a
fixup(scenario-from-ticket): shorten description to trigger condition…
dmihalcik-virtru May 22, 2026
a681c52
feat(.claude/skills): full overhaul + new scenario-doctor (DSPX-3302)
dmihalcik-virtru May 29, 2026
5f31811
fix(.claude/skills): align scenario-* with self-provisioning init + c…
dmihalcik-virtru Jun 2, 2026
d3caf81
fix(.claude/skills): add --instance parameter to scenario run examples
dmihalcik-virtru Jun 2, 2026
de2ee74
fix(install,up,doctor): ensure otdf-sdk-mgr builds are used (DSPX-3302)
dmihalcik-virtru Jun 8, 2026
329cd9f
fixup cleanups
dmihalcik-virtru Jun 8, 2026
ee7e302
style(.claude/skills): format all shell scripts with shfmt
dmihalcik-virtru Jun 8, 2026
7291b36
fixup shfmt
dmihalcik-virtru Jun 8, 2026
17939cf
fixup shfmt
dmihalcik-virtru Jun 8, 2026
8b69e57
Revert "fixup shfmt"
dmihalcik-virtru Jun 8, 2026
8278da5
Revert "fixup shfmt"
dmihalcik-virtru Jun 8, 2026
db398f1
Revert "style(.claude/skills): format all shell scripts with shfmt"
dmihalcik-virtru Jun 8, 2026
3c72360
style: fix shfmt formatting by removing keep_padding from .editorconfig
dmihalcik-virtru Jun 8, 2026
ffa6f54
feat(xtest): Adds pure mlkem test scenarios
dmihalcik-virtru May 29, 2026
f7d8f54
fixup: adds scneario file
dmihalcik-virtru May 29, 2026
0409534
feat(scenario): enable KAS preview features configuration
dmihalcik-virtru Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .claude/plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "opentdf-test-harness",
"version": "0.1.0",
"description": "Jira-ticket-driven scenarios for the OpenTDF test harness. Pulls ticket context from Jira (acli) — any ticket type, including bugs, feature stories, and PR-driven work — provisions pinned platform/KAS/SDK versions or refs (released versions, main, feature branches, PR SHAs), runs the xtest pytest suite, and tears down. Useful for QA, platform/SDK developers writing tests for new features first, and downstream first/third-party integrators.",
"skills_dir": "../skills",
"skills": [
"feature-design",
"scenario-from-ticket",
"scenario-matrix",
"scenario-up",
"scenario-run",
"scenario-tear-down",
"instance-status"
],
"requirements": [
"uv (python package manager) on PATH",
"go toolchain (platform binaries are built from source)",
"git (for worktrees of opentdf/platform)",
"docker (for keycloak/postgres dependencies)",
"acli (Atlassian CLI; needed for the scenario-from-ticket skill)",
"gh (GitHub CLI; needed for scenario-matrix to resolve PR refs)"
],
"permissions": {
"allow": [
"Bash(uv run otdf-local *)",
"Bash(uv run otdf-sdk-mgr *)",
"Bash(uv run pytest *)",
"Bash(acli jira workitem view *)",
"Bash(acli jira workitem search *)",
"Bash(acli jira workitem comment list *)",
"Bash(acli jira workitem comment create *)",
"Bash(acli jira workitem attachment list *)",
"Bash(acli jira workitem link list *)",
"Bash(acli jira project view *)",
"Skill(feature-design)",
"Skill(scenario-from-ticket)",
"Skill(scenario-matrix)",
"Skill(scenario-up)",
"Skill(scenario-run)",
"Skill(scenario-tear-down)",
"Skill(instance-status)",
"Write(xtest/scenarios/**)",
"Write(xtest/features/**)",
"Write(xtest/bugs/*_test.py)",
"Write(tests/instances/**)"
]
},
"permission_notes": "acli jira write-paths intentionally excluded: edit/delete/transition/assign/archive/clone/create/create-bulk/link create/watcher add/comment update/comment delete. Add them explicitly via .claude/settings.local.json if your team needs them; the default plugin is read+comment only."
}
34 changes: 34 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"permissions": {
"allow": [
"Bash(uv run otdf-local *)",
"Bash(uv run otdf-sdk-mgr *)",
"Bash(uv run pytest *)",
"Bash(uv sync *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git show *)",
"Bash(gh api *)",
"Bash(gh issue view *)",
"Bash(gh pr view *)",
"Bash(gh run *)",
"Bash(acli jira workitem view *)",
"Bash(acli jira workitem search *)",
"Bash(acli jira workitem comment list *)",
"Bash(acli jira workitem comment create *)",
"Bash(acli jira workitem attachment list *)",
"Bash(acli jira workitem link list *)",
"Bash(acli jira workitem watcher list *)",
"Bash(acli jira project view *)",
"Bash(acli jira board view *)",
"Bash(acli jira sprint view *)",
"Skill(*)",
"Write(xtest/scenarios/**)",
"Write(xtest/features/**)",
"Write(xtest/bugs/*_test.py)",
"Write(tests/instances/**)",
"Write(.claude/tmp/**)"
]
}
}
118 changes: 118 additions & 0 deletions .claude/skills/feature-design/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
name: feature-design
description: This skill should be used when the user asks to "design a cross-repo feature", "set up a feature spec", "draft a feature across platform and SDKs", "design a fix that spans repos", or wants the tests-side artifacts + per-repo todo lists set up in one pass for work that crosses platform + Go/Java/JS SDKs. Hands off to `feature-orchestrate` for the per-repo PR work.
allowed-tools: Bash, Read, Write, Edit, Grep, Glob, Skill
---

# feature-design

Turn a fuzzy "let's build X across the OpenTDF repos" into a concrete bundle of artifacts that pin down the tests-side work first and stage the cross-repo work for handoff to `feature-orchestrate`.

Two ideas to internalize before reading the steps:

1. **Tests-side artifacts land first, dormant.** The scenario + draft test + `feature_type` entry merge to `tests/main` as a regular PR. They stay "all skipped" until each SDK opens its own PR adding a `supports <feature>` case to its `cli.sh` source — that PR's CI activates the test for that SDK. No cross-PR lockstep coordination; per-repo PRs land async, in any order.
2. **Propose, don't ask.** Draft a complete spec from the Jira ticket on the first pass and let the user redirect what's wrong in a single revision. Only ask one composite question. If information is missing that can't be filled in (no Jira ticket, ambiguous scope, unclear feature name), bail — don't fabricate.

## Inputs

- Jira key (Story/Task usually; Bug works the same way), OR a free-text description of the feature.
- (Optional) explicit list of repos to scope to, if the user wants something tighter than the default.

## Steps

### Step 1 — Pull the Jira context

If a Jira key was given, run both — `view` takes the key positionally, `comment list` requires `--key`; comments often carry scope refinements:

```bash
acli jira workitem view <JIRA-KEY> --fields '*all' --json
acli jira workitem comment list --key <JIRA-KEY>
```

Extract Issue Type, summary, description, status, and any comments about scope or implementation notes. If no Jira key, the user's description IS the spec input.

### Step 2 — Propose a complete draft

Draft the full spec body and the per-repo todo lists inline in the reply. Don't ask the user one field at a time — produce a complete first draft they can react to:

- **Feature flag name** — snake_case identifier derived from the Jira summary. Becomes the `supports("<name>")` gate string AND the `feature_type` entry in `xtest/tdfs.py`. Validate it's a valid Python identifier and doesn't collide with an existing `feature_type` member.
- **Touched repos** — default set is `tests, platform, sdk-go, sdk-java, sdk-web`. Trim or expand based on what the ticket says. Pure platform features skip the SDK repos; pure SDK-only features skip platform; `tests` is always present (the dormant scenario + tdfs.py entry has to live there).
- **Per-repo todo lists** — 2–4 bullets per repo:
- `tests` — register the feature in `feature_type`, author the scenario, draft the test gated on `supports("<feature>")`.
- `platform` — service-side implementation (KAS path, policy plumbing, etc.) and any env-var handling in the dev harness (e.g. honoring `XT_WITH_<FEATURE>`).
- `sdk-go` / `sdk-java` / `sdk-web` — encrypt/decrypt path implementation, plus a `supports <feature>` case in that SDK's `cli.sh` source. **Don't pin the version bound in the spec** — the implementing engineer sets the `awk` predicate at PR time, since the bound depends on which release will ship the impl.
- **Branch name** — `<JIRA-KEY>-<feature-slug>`, the same string across every touched repo so `feature-orchestrate` (and the user) can find each repo's PR by branch alone.

Present the draft, then ask exactly one composite question: "Anything to redirect — feature name, touched repos, todo items, branch?" Apply edits in a single revision rather than turn-by-turn. The user can always drop into plain chat if they want to think out loud — answer normally and re-invoke this skill once the design firms up.

If no Jira key was given AND the user's description doesn't pin down a clear scope (feature flag name, touched repos, intended behavior), bail rather than fabricate:

```
I need either (a) a Jira Story/Task/Bug key, or (b) a description that names
the feature flag, the repos it touches, and the intended behavior. Add either
and re-invoke this skill.
```

### Step 3 — Write the spec

Write `xtest/features/<feature-name>.yaml`. Shape (still informal — no Pydantic model yet):

```yaml
apiVersion: opentdf.io/v1alpha1
kind: Feature
metadata:
name: <feature-name> # supports() string + feature_type entry, snake_case
jira: <JIRA-KEY> # omit if no ticket
title: "<one-line title>"
created: <YYYY-MM-DD>
repos:
tests:
branch: <JIRA-KEY>-<feature-slug>
todo:
- Register "<feature-name>" in xtest/tdfs.py feature_type
- Author scenario + draft test (via scenario-from-ticket)
platform:
branch: <JIRA-KEY>-<feature-slug>
todo: [ ... ]
sdk-go:
branch: <JIRA-KEY>-<feature-slug>
todo:
- Implement <feature> in the encrypt/decrypt path
- Add `supports <feature>` case to cli.sh with version-bound awk predicate
sdk-java: { branch: ..., todo: [ ... ] }
sdk-web: { branch: ..., todo: [ ... ] }
scenarios:
- xtest/scenarios/<jira-key-lowercased>.yaml
```

PR status (open/merged/CI passing) deliberately is NOT in the spec — it's auto-discovered from `gh pr list --search "head:<branch>"` per repo whenever something asks "where are we?" The spec is a declaration of intent.

### Step 4 — Drive the tests-side artifacts

In this order, so each step's output feeds the next:

1. **Add the feature flag to `xtest/tdfs.py`**. Find the `feature_type` Literal alias near the top of the file. Insert the new entry alphabetically. Don't touch any `cli.sh` files — `supports <feature>` cases land per-SDK in their own PRs.

2. **Invoke `scenario-from-ticket`** via the Skill tool (`skill: scenario-from-ticket`, `args: <JIRA-KEY>`). It runs its Story/Task branch and produces the scenario + draft test gated on `supports("<feature>")`. If no Jira key was given, draft the scenario directly using the same shape (`xtest/scenarios/<feature-name>.yaml`).

3. **Validate the scenario**:

```bash
uv run python -m otdf_sdk_mgr.schema validate xtest/scenarios/<jira-key>.yaml
```

### Step 5 — Report

One block summarizing:

- The spec path (`xtest/features/<feature-name>.yaml`).
- The scenario + draft test paths.
- The line(s) added to `xtest/tdfs.py`.
- A one-liner suggesting next steps: `feature-orchestrate xtest/features/<feature-name>.yaml` (for per-repo PR work), or `scenario-up xtest/scenarios/<id>.yaml` + `scenario-doctor <id>` (to bring the dormant scenario up against `main` and confirm "all skipped" baseline before SDK work starts).

## Notes

- This skill produces **tests-side artifacts only**. It does NOT create branches in other repos, does NOT open PRs, does NOT install platform/SDK builds. That's `feature-orchestrate`'s job.
- Bugs that span repos use the same shape — pass the Bug ticket key and `scenario-from-ticket`'s Bug branch fills `expected:` / `actual:` from the reproduction prose. The cross-repo gating still works: tests land dormant, each per-repo PR activates them by adding the supports case as part of the fix.
- For an existing spec being revised, read it first and propose a diff rather than a full rewrite. The tests-side artifacts (scenario, tdfs.py entry) usually shouldn't be regenerated — edit them surgically.
- If the user starts the conversation by describing the feature in plain chat rather than invoking this skill, answer normally — re-invoke the skill once the scope firms up. Don't gatekeep.
80 changes: 80 additions & 0 deletions .claude/skills/instance-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
name: instance-status
description: This skill should be used when the user asks "what's running", "check ports", "show instance status", "list test instances", "are any services up", or before invoking `scenario-up` to detect port collisions (including from sibling git worktrees). For deeper "does the running env match what the scenario yaml says" verification, defer to `scenario-doctor` instead.
allowed-tools: Bash, Read
---

# instance-status

Report a snapshot of test environment state: which instances are defined in this worktree, what is actually listening on the conventional ports (regardless of which worktree owns it), and whether each service is healthy. Surface port collisions before they bite `scenario-up`.

## Process

### Step 0 — Cross-worktree probe (always first)

`otdf-local instance ls` is scoped to the current worktree's `tests/instances/`. Sibling worktrees' running services are invisible to that listing but very much listening on the host's ports. Probe the host directly:

```bash
bash ${CLAUDE_PLUGIN_ROOT:-.}/skills/instance-status/scripts/cross-worktree-probe.sh
```

Output is tab-separated, one row per listener:

```
port proto pid cwd kind
8080 tcp 28656 /Users/.../reproducing-things/... platform
8585 tcp 28684 /Users/.../reproducing-things/... kas
compose docker - main compose-project
```

Carry forward two facts into the rest of the report:
1. Which of the conventional ports (`8080`, `8181..8686`, `5432`, `8888`) are occupied.
2. The owning `cwd` for each — when it differs from the current worktree, label the line as **foreign** in the final summary so the user knows to tear that down before re-using the port.

### Step 1 — List instances on disk

```bash
uv run otdf-local instance ls --json
```

Each entry includes `name`, `platform` version, `ports_base`, and the `kas:` keys. Two checks:
- Flag any two local instances that share a `ports_base` — they cannot run concurrently.
- Note: this listing is **worktree-scoped**. The cross-worktree probe from Step 0 is the source of truth for "what's actually using port X."

### Step 2 — Per-instance status

For each local instance from Step 1:

```bash
uv run otdf-local --instance <name> status --json
```

Each service reports `running`, `healthy`, and the bound port. Run sequentially (a status query is cheap; parallel adds nothing). Cross-reference each "running" entry with Step 0's table — if the port shows `kind=platform` but the owning `cwd` is a sibling worktree, the local instance's status reading is misleading (it's reporting on someone else's binary).

### Step 3 — Summarize

Compose the reply in this order:
1. **Cross-worktree listeners** — the Step 0 table, with each foreign row labeled. Skip if no ports are occupied.
2. **Local instances** — one short block per instance: service → port → state (running/healthy). Mark each row's port as `local` or `foreign` based on Step 0's owner.
3. **Port-base collisions** — any pair of local instances with the same `ports_base`, recommending a re-init: `uv run otdf-local instance init <name> --from-scenario <path> --ports-base <new>`.
4. **Unhealthy rows** — each with the path to its log (e.g. `tests/instances/<name>/logs/kas-alpha.log`).

Skip empty sections rather than print "(none)".

## When ports collide

If Step 0 shows a foreign listener on a port the user is about to use, two paths:
- Tear down the foreign instance first. Find the owning worktree from the `cwd` column; cd there and run `OTDF_LOCAL_INSTANCE_NAME=<name> uv run otdf-local down`.
- Or pick a different ports base for the new instance: `uv run otdf-local instance init <name> --from-scenario <path> --ports-base 9080` (or any free base).

If `otdf-local instance init` warns about a local collision at creation time, it doesn't enforce it; re-running with `--ports-base <new>` is the fix.

## What this skill does NOT do

For the deeper question "is the binary serving port X actually the one my scenario YAML pinned?", use `scenario-doctor` — that skill diffs the running service's `.version` sidecar against the instance's expected pin. `instance-status` reports *what's listening*, not *whether it's the right thing*.

## Additional Resources

### Script

- **`scripts/cross-worktree-probe.sh`** — surveys conventional ports + docker compose projects across all worktrees on this host. Always run first in Step 0. Tab-separated stdout (header on line 1).
57 changes: 57 additions & 0 deletions .claude/skills/instance-status/scripts/cross-worktree-probe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# cross-worktree-probe.sh — surface listeners on opentdf test ports across ALL worktrees.
#
# `otdf-local instance ls` is scoped to one worktree's tests/instances/; sibling
# worktrees' running services are invisible to it. This script probes the host
# directly so the agent can detect cross-worktree port collisions before
# `scenario-up` (or explain why a port appears "free" from one CLI but isn't).
#
# Output: tab-separated, one record per line, header on first line.
# Columns: port proto pid cwd kind
# kind ∈ { platform | kas | docker-keycloak | docker-postgres | unknown }

set -u

PORTS=(8080 8181 8282 8383 8484 8585 8686 5432 8888)

printf 'port\tproto\tpid\tcwd\tkind\n'

for port in "${PORTS[@]}"; do
# -F to use parseable format; -n -P to skip name resolution (faster)
while IFS= read -r line; do
[[ -z "$line" ]] && continue
pid="$(awk '{print $2}' <<<"$line")"
[[ -z "$pid" || "$pid" == "PID" ]] && continue

cwd="$(lsof -p "$pid" -d cwd -Fn 2>/dev/null | awk '/^n/ { sub(/^n/,""); print; exit }')"
cwd="${cwd:-?}"

cmd="$(ps -o command= -p "$pid" 2>/dev/null | head -c 200)"
case "$port" in
8080) kind=platform ;;
8181 | 8282 | 8383 | 8484 | 8585 | 8686) kind=kas ;;
8888) kind=docker-keycloak ;;
5432) kind=docker-postgres ;;
*) kind=unknown ;;
esac
# Refine kind if process command says otherwise (e.g. a misbound port).
case "$cmd" in
*"/service "*) kind=platform ;;
*opentdf-kas* | *"kas start"*) kind=kas ;;
esac

printf '%s\ttcp\t%s\t%s\t%s\n' "$port" "$pid" "$cwd" "$kind"
done < <(lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null | tail -n +2)
done

# Docker compose projects sharing the host docker daemon — names like
# `<project>-keycloak-1`, `<project>-opentdfdb-1`. The project is whatever
# directory `docker compose` was invoked from (typically a worktree's
# xtest/platform/src/<ref>/ directory).
docker ps --format '{{.Names}}' 2>/dev/null | while IFS= read -r name; do
[[ -z "$name" ]] && continue
case "$name" in
*-keycloak-*) printf 'compose\tdocker\t-\t%s\tcompose-project\n' "${name%-keycloak-*}" ;;
*-opentdfdb-*) printf 'compose\tdocker\t-\t%s\tcompose-project\n' "${name%-opentdfdb-*}" ;;
esac
done | sort -u
Loading
Loading