Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbcb1b3778 | |||
| 39052bf47e | |||
| 7cd5edd650 | |||
| 2d41748006 | |||
| 1d4be9988e | |||
| ed65aa6474 | |||
| 4c5536ab78 | |||
| b01180ea83 | |||
| 07fab5586b | |||
| 6debe7e641 | |||
| 11c349d9ae | |||
| 36823c91fb | |||
| 17421bc5f3 | |||
| bd62f360a7 | |||
| 5a11b34aad | |||
| f9cf5c3861 | |||
| ea223fcf98 | |||
| 8d416558a3 | |||
| 561a84fcf8 | |||
| 3c405f0524 | |||
| 5ac704c4c4 | |||
| 5af6ba7d89 | |||
| 06dc273896 | |||
| 7e7940db9a | |||
| 721365e59d | |||
| 418da456d7 | |||
| 6de470b53d | |||
| 3b4933256e | |||
| 6f7136724f | |||
| 9bcf18331e | |||
| 5525fe875e | |||
| b81bd28865 | |||
| 9b82eb7da2 | |||
| f8d00e2f20 | |||
| f5fa607c35 | |||
| 43a68e97ee | |||
| b1b0fc7455 | |||
| afcf58331f | |||
| b31955fc97 | |||
| 1777821e8b | |||
| 79d38606fe | |||
| 8cba751835 | |||
| 8175ed87d0 | |||
| b64010e729 | |||
| 1cfe0c5a01 | |||
| 0e2c0b0b82 | |||
| cd9ee78359 | |||
| 23e1df4cfb | |||
| 2023c91ec3 | |||
| 67519051f1 | |||
| 6ae1d7cd5f | |||
| ddfc3dd4c7 | |||
| 990ca0b71a | |||
| 61bc467a03 | |||
| 0bd116b6b7 |
@@ -1,26 +1,50 @@
|
|||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- GO_IMAGE: golang:1.26.8-bookworm
|
||||||
|
|
||||||
when:
|
when:
|
||||||
- event: tag
|
- event: tag
|
||||||
|
ref: refs/tags/v*
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build-release-assets
|
- name: deliver
|
||||||
image: golang:1.25.5
|
image: ${GO_IMAGE}
|
||||||
commands:
|
environment:
|
||||||
- bash scripts/build-release.sh "$CI_COMMIT_TAG" dist
|
RELEASEKIT_GITEA_TOKEN:
|
||||||
|
|
||||||
- name: publish-release
|
|
||||||
image: woodpeckerci/plugin-release
|
|
||||||
depends_on:
|
|
||||||
- build-release-assets
|
|
||||||
settings:
|
|
||||||
api_key:
|
|
||||||
from_secret: GITEA_RELEASE_TOKEN
|
from_secret: GITEA_RELEASE_TOKEN
|
||||||
files:
|
commands:
|
||||||
- dist/audita-*-linux-amd64
|
- |
|
||||||
- dist/audita-*-linux-arm64
|
set -eu
|
||||||
note: docs/releases/${CI_COMMIT_TAG}.md
|
test -n "$${CI_COMMIT_TAG}"
|
||||||
checksum: sha256
|
test -n "$${CI_COMMIT_SHA}"
|
||||||
checksum-file: SHA256SUMS
|
git check-ref-format "refs/tags/$${CI_COMMIT_TAG}"
|
||||||
checksum-flatten: true
|
git fetch origin "refs/tags/$${CI_COMMIT_TAG}:refs/tags/$${CI_COMMIT_TAG}"
|
||||||
file-exists: skip
|
tag_commit=$(git rev-parse "refs/tags/$${CI_COMMIT_TAG}^{commit}")
|
||||||
overwrite: false
|
expected_commit=$(git rev-parse --verify "$${CI_COMMIT_SHA}^{commit}")
|
||||||
prerelease: false
|
test "$(printf '%s' "$${tag_commit}" | tr '[:upper:]' '[:lower:]')" = "$${expected_commit}"
|
||||||
|
git checkout --detach "$${tag_commit}"
|
||||||
|
test "$(git rev-parse HEAD | tr '[:upper:]' '[:lower:]')" = "$${expected_commit}"
|
||||||
|
test -z "$(git status --porcelain --untracked-files=all)"
|
||||||
|
|
||||||
|
bootstrap_dir=$(mktemp -d "$${CI_WORKSPACE%/*}/.releasekit-bootstrap.XXXXXX")
|
||||||
|
trap 'rm -rf "$${bootstrap_dir}"' EXIT
|
||||||
|
cp go.mod "$${bootstrap_dir}/go.mod"
|
||||||
|
if [ -f go.sum ]; then
|
||||||
|
cp go.sum "$${bootstrap_dir}/go.sum"
|
||||||
|
else
|
||||||
|
: > "$${bootstrap_dir}/go.sum"
|
||||||
|
fi
|
||||||
|
cp "$${bootstrap_dir}/go.mod" "$${bootstrap_dir}/releasekit.mod"
|
||||||
|
cp "$${bootstrap_dir}/go.sum" "$${bootstrap_dir}/releasekit.sum"
|
||||||
|
|
||||||
|
export GOWORK=off
|
||||||
|
export GOTOOLCHAIN=local
|
||||||
|
releasekit_module=gitea.maximumdirect.net/eric/ReleaseKit
|
||||||
|
go list -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" -m "$${releasekit_module}"
|
||||||
|
go build -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" \
|
||||||
|
-o "$${bootstrap_dir}/releasekit" "$${releasekit_module}/cmd/releasekit"
|
||||||
|
"$${bootstrap_dir}/releasekit" ci-deliver \
|
||||||
|
--repo "$${CI_WORKSPACE}" \
|
||||||
|
--prepared \
|
||||||
|
--commit "$${expected_commit}" \
|
||||||
|
"$${CI_COMMIT_TAG}"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ reports for review and subprocess integration.
|
|||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
From a checkout, build with Go 1.25.5 or newer:
|
From a checkout, build with Go 1.26.0 or newer:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go build -o ./bin/audita ./cmd/audita
|
go build -o ./bin/audita ./cmd/audita
|
||||||
@@ -15,7 +15,7 @@ go build -o ./bin/audita ./cmd/audita
|
|||||||
Processing requires a reachable [compatible LLM endpoint](docs/integrations/openai-compatible-llm.md)
|
Processing requires a reachable [compatible LLM endpoint](docs/integrations/openai-compatible-llm.md)
|
||||||
and access to a model you select. The [minimal config](examples/minimal-config.yml)
|
and access to a model you select. The [minimal config](examples/minimal-config.yml)
|
||||||
targets an unauthenticated local endpoint; edit its endpoint and
|
targets an unauthenticated local endpoint; edit its endpoint and
|
||||||
[local profiles](examples/profiles/) to match your service and models. The
|
[local profiles](examples/profiles/local/) to match your service and models. The
|
||||||
[service example](examples/production-config.yml) demonstrates a named credential
|
[service example](examples/production-config.yml) demonstrates a named credential
|
||||||
reference and a shared backend. Set its referenced environment variable before
|
reference and a shared backend. Set its referenced environment variable before
|
||||||
validating it. The [testing overlay](examples/overlays/testing.yml) selects both
|
validating it. The [testing overlay](examples/overlays/testing.yml) selects both
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/cli"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/cli"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/cli"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ func newProcessFixture(t *testing.T, mode, modules string) processFixture {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, id := range []string{"proposal", "validation"} {
|
for _, id := range []string{"proposal", "validation"} {
|
||||||
profile := fmt.Sprintf("id: %s\nbackend: local\nmodel: local\ntimeout_seconds: 5\n", id)
|
profile := fmt.Sprintf("profiles:\n %s:\n backend: local\n model: local\n timeout_seconds: 5\n", id)
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", id+".yaml"), []byte(profile), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", id+".yaml"), []byte(profile), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -293,6 +293,25 @@ func (f processFixture) run(t *testing.T, includeOutput bool, extra ...string) (
|
|||||||
return code, stdout.String(), stderr.String()
|
return code, stdout.String(), stderr.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f processFixture) runConfigView(t *testing.T, view string) (int, string, string) {
|
||||||
|
t.Helper()
|
||||||
|
command := exec.Command(os.Args[0], "-test.run=^TestProcessSubprocessHelper$", "--", "config", "print-effective", "--config", f.config, "--view", view)
|
||||||
|
command.Dir = f.dir
|
||||||
|
command.Env = append(os.Environ(), "AUDITA_HELPER_PROCESS=1", "AUDITA_TEST_PROVIDER_KEY="+f.provider.secret)
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
command.Stdout, command.Stderr = &stdout, &stderr
|
||||||
|
err := command.Run()
|
||||||
|
code := 0
|
||||||
|
if err != nil {
|
||||||
|
if exit, ok := err.(*exec.ExitError); ok {
|
||||||
|
code = exit.ExitCode()
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return code, stdout.String(), stderr.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (f processFixture) errors(t *testing.T) reporting.ErrorsDocument {
|
func (f processFixture) errors(t *testing.T) reporting.ErrorsDocument {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
raw, err := os.ReadFile(filepath.Join(f.dir, "errors.yml"))
|
raw, err := os.ReadFile(filepath.Join(f.dir, "errors.yml"))
|
||||||
@@ -799,15 +818,70 @@ func TestSubprocessStdoutAndSelectedReportDirectory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubprocessUsesIndependentTranscriptReportAndDebugDestinations(t *testing.T) {
|
||||||
|
f := newProcessFixture(t, "normal", "[grammar]")
|
||||||
|
transcriptDir := filepath.Join(f.dir, "transcripts")
|
||||||
|
reportDir := filepath.Join(f.dir, "reports")
|
||||||
|
debugDir := filepath.Join(f.dir, "debug")
|
||||||
|
for _, dir := range []string{transcriptDir, reportDir} {
|
||||||
|
if err := os.Mkdir(dir, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.output = filepath.Join(transcriptDir, "corrected.json")
|
||||||
|
code, stdout, stderr := f.run(t, true, "--report-dir", reportDir, "--debug", "--work-dir", debugDir)
|
||||||
|
if code != 0 || stdout != "" || !strings.HasPrefix(stderr, "Completed:") {
|
||||||
|
t.Fatalf("code=%d stdout=%q stderr=%q", code, stdout, stderr)
|
||||||
|
}
|
||||||
|
if output, err := os.ReadFile(f.output); err != nil || !bytes.Contains(output, []byte("Hello world")) {
|
||||||
|
t.Fatalf("transcript: %s %v", output, err)
|
||||||
|
}
|
||||||
|
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
||||||
|
if _, err := os.Stat(filepath.Join(reportDir, name)); err != nil {
|
||||||
|
t.Fatalf("report %s: %v", name, err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(transcriptDir, name)); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("report %s appeared beside transcript: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dirs, err := filepath.Glob(filepath.Join(debugDir, "run-*"))
|
||||||
|
if err != nil || len(dirs) != 1 {
|
||||||
|
t.Fatalf("debug runs: %v %v", dirs, err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(dirs[0], "correction-ledger.json")); err != nil {
|
||||||
|
t.Fatalf("debug capture: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubprocessReportingAndDebugSelections(t *testing.T) {
|
func TestSubprocessReportingAndDebugSelections(t *testing.T) {
|
||||||
var expectedOutput, expectedSummary string
|
var expectedOutput, expectedSummary string
|
||||||
for _, selection := range []struct{ reporting, debug bool }{
|
for _, selection := range []struct{ reporting, debug bool }{
|
||||||
{false, false}, {false, true}, {true, false}, {true, true},
|
{false, false}, {false, true}, {true, false}, {true, true},
|
||||||
} {
|
} {
|
||||||
f := newProcessFixture(t, "normal", "[grammar]")
|
f := newProcessFixture(t, "normal", "[grammar]")
|
||||||
extra := []string{"--reporting=false", "--report-dir", filepath.Join(f.dir, "missing")}
|
if selection.reporting && selection.debug {
|
||||||
|
original, err := os.ReadFile(f.config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
old := fmt.Sprintf("backends:\n local:\n endpoint: %s/v1\n credential: AUDITA_TEST_PROVIDER_KEY", f.server.URL)
|
||||||
|
inherited := fmt.Sprintf("backends:\n local-base:\n endpoint: %s/v1\n credential: AUDITA_TEST_PROVIDER_KEY\n concurrency: 2\n queue_capacity: 0\n local:\n extends: local-base\n concurrency: 1", f.server.URL)
|
||||||
|
updated := strings.Replace(string(original), old, inherited, 1)
|
||||||
|
if updated == string(original) {
|
||||||
|
t.Fatal("process fixture backend configuration was not found")
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(f.config, []byte(updated), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reportDir := filepath.Join(f.dir, "missing")
|
||||||
|
extra := []string{"--reporting=false", "--report-dir", reportDir}
|
||||||
if selection.reporting {
|
if selection.reporting {
|
||||||
extra = []string{"--reporting=true", "--report-dir", filepath.Join(f.dir, "missing")}
|
reportDir = filepath.Join(f.dir, "selected-reports")
|
||||||
|
if err := os.Mkdir(reportDir, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
extra = []string{"--reporting=true", "--report-dir", reportDir}
|
||||||
}
|
}
|
||||||
if selection.debug {
|
if selection.debug {
|
||||||
extra = append(extra, "--debug")
|
extra = append(extra, "--debug")
|
||||||
@@ -827,7 +901,7 @@ func TestSubprocessReportingAndDebugSelections(t *testing.T) {
|
|||||||
t.Fatalf("selection changed processing: %+v summary=%q output=%s", selection, summary, output)
|
t.Fatalf("selection changed processing: %+v summary=%q output=%s", selection, summary, output)
|
||||||
}
|
}
|
||||||
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
||||||
_, err := os.Stat(filepath.Join(f.dir, name))
|
_, err := os.Stat(filepath.Join(reportDir, name))
|
||||||
if selection.reporting && err != nil || !selection.reporting && !os.IsNotExist(err) {
|
if selection.reporting && err != nil || !selection.reporting && !os.IsNotExist(err) {
|
||||||
t.Fatalf("selection=%+v report %s: %v", selection, name, err)
|
t.Fatalf("selection=%+v report %s: %v", selection, name, err)
|
||||||
}
|
}
|
||||||
@@ -842,6 +916,31 @@ func TestSubprocessReportingAndDebugSelections(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if selection.reporting {
|
||||||
|
effective, err := os.ReadFile(filepath.Join(dirs[0], "effective-config.redacted.yaml"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(effective), f.server.URL+"/v1") || !strings.Contains(string(effective), "concurrency: 1") || !strings.Contains(string(effective), "queue_capacity: 0") || strings.Contains(string(effective), f.provider.secret) {
|
||||||
|
t.Fatalf("effective inherited settings: %s", effective)
|
||||||
|
}
|
||||||
|
origins, err := os.ReadFile(filepath.Join(dirs[0], "config-origins.yaml"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"from: /backends/local-base/endpoint", "to: /backends/local/endpoint", "reference: /backends/local/extends"} {
|
||||||
|
if !strings.Contains(string(origins), want) {
|
||||||
|
t.Fatalf("configuration origins missing %q: %s", want, origins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(string(origins), f.provider.secret) {
|
||||||
|
t.Fatal("configuration origins exposed the resolved inherited credential")
|
||||||
|
}
|
||||||
|
code, explain, stderr := f.runConfigView(t, "explain")
|
||||||
|
if code != 0 || stderr != "" || !strings.Contains(explain, "from: /backends/local-base/endpoint") || strings.Contains(explain, f.provider.secret) {
|
||||||
|
t.Fatalf("inheritance explanation: code=%d stdout=%s stderr=%s", code, explain, stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1011,6 +1110,63 @@ func TestSubprocessInvalidInputLeavesFailedReports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubprocessMissingInheritanceParentFailsBeforeProviderDispatch(t *testing.T) {
|
||||||
|
f := newProcessFixture(t, "normal", "[grammar]")
|
||||||
|
original, err := os.ReadFile(f.config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
updated := strings.Replace(string(original), " local:\n endpoint:", " local:\n extends: missing\n endpoint:", 1)
|
||||||
|
if updated == string(original) {
|
||||||
|
t.Fatal("process fixture backend configuration was not found")
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(f.config, []byte(updated), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
code, stdout, stderr := f.run(t, true)
|
||||||
|
if code != 2 || stdout != "" || f.provider.callCount() != 0 || !strings.Contains(stderr, "referenced sibling does not exist") {
|
||||||
|
t.Fatalf("missing parent handling: code=%d stdout=%q stderr=%q provider calls=%d", code, stdout, stderr, f.provider.callCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubprocessInvalidInputWritesFailedReportsToSelectedDirectory(t *testing.T) {
|
||||||
|
f := newProcessFixture(t, "normal", "[grammar]")
|
||||||
|
if err := os.WriteFile(f.transcript, []byte("not JSON"), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
reportDir := filepath.Join(f.dir, "failed-reports")
|
||||||
|
if err := os.Mkdir(reportDir, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
code, _, _ := f.run(t, true, "--report-dir", reportDir)
|
||||||
|
if code != 1 || f.provider.callCount() != 0 {
|
||||||
|
t.Fatalf("invalid input dispatched: code=%d calls=%d", code, f.provider.callCount())
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(filepath.Join(reportDir, "errors.yml"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var document reporting.ErrorsDocument
|
||||||
|
if err := yaml.Unmarshal(raw, &document); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if document.Status != reporting.StatusFailed || len(document.Errors) != 1 || document.Errors[0].Scope != "run" {
|
||||||
|
t.Fatalf("failed report: %+v", document)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(reportDir, "recoveries.yml")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if raw, err := os.ReadFile(filepath.Join(reportDir, "report.md")); err != nil || !bytes.Contains(raw, []byte("Status: failed")) {
|
||||||
|
t.Fatalf("Markdown status: %s %v", raw, err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(f.dir, "errors.yml")); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("failed report appeared in default directory: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(f.output); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("failed transcript published: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubprocessOutputFailureBeforeModelDispatch(t *testing.T) {
|
func TestSubprocessOutputFailureBeforeModelDispatch(t *testing.T) {
|
||||||
f := newProcessFixture(t, "normal", "[grammar]")
|
f := newProcessFixture(t, "normal", "[grammar]")
|
||||||
f.output = filepath.Join(f.dir, "missing", "output.json")
|
f.output = filepath.Join(f.dir, "missing", "output.json")
|
||||||
@@ -1243,4 +1399,47 @@ func TestSubprocessProfileSources(t *testing.T) {
|
|||||||
if code != 2 || !bytes.Equal(before, after) || f.provider.callCount() != 0 {
|
if code != 2 || !bytes.Equal(before, after) || f.provider.callCount() != 0 {
|
||||||
t.Fatalf("loaded profile not protected: %d %s", code, stderr)
|
t.Fatalf("loaded profile not protected: %d %s", code, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parentDir := filepath.Join(f.dir, "profiles", "catalog")
|
||||||
|
childDir := filepath.Join(f.dir, "profiles", "nested", "child")
|
||||||
|
for _, directory := range []string{parentDir, childDir} {
|
||||||
|
if err := os.MkdirAll(directory, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent := filepath.Join(parentDir, "arbitrary-name.yaml")
|
||||||
|
child := filepath.Join(childDir, "override.yaml")
|
||||||
|
resource := filepath.Join(parentDir, "report.md")
|
||||||
|
for path, content := range map[string]string{
|
||||||
|
parent: "prompts:\n source:\n release/1~draft:\n messages:\n - role: user\n content_file: ./report.md\n output:\n format: text\n validation_mode: basic\n",
|
||||||
|
child: "prompts:\n literal/~child:\n extends: source\n",
|
||||||
|
resource: "Captured operator message.\n",
|
||||||
|
} {
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourceBefore, err := os.ReadFile(resource)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.output = resource
|
||||||
|
code, _, stderr = f.run(t, true, "--reporting=false")
|
||||||
|
resourceAfter, err := os.ReadFile(resource)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if code != 2 || !bytes.Equal(resourceBefore, resourceAfter) || f.provider.callCount() != 0 {
|
||||||
|
t.Fatalf("consumed message file not protected as transcript output: %d %s", code, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.output = filepath.Join(f.dir, "output.json")
|
||||||
|
code, _, stderr = f.run(t, true, "--report-dir", parentDir)
|
||||||
|
resourceAfter, err = os.ReadFile(resource)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if code != 2 || !bytes.Equal(resourceBefore, resourceAfter) || f.provider.callCount() != 0 {
|
||||||
|
t.Fatalf("consumed message file not protected as report output: %d %s", code, stderr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
docs/adr/0001-releasekit.md
Normal file
45
docs/adr/0001-releasekit.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# ADR 0001: Adopt ReleaseKit for release management
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted.
|
||||||
|
|
||||||
|
## Date
|
||||||
|
|
||||||
|
2026-10-07
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Audita maintains custom release scripts, build tooling, delivery configuration,
|
||||||
|
and tests for mechanics shared across applications in the same suite.
|
||||||
|
[ReleaseKit](https://gitea.maximumdirect.net/eric/ReleaseKit) provides a common implementation and standard workflow for
|
||||||
|
those responsibilities.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Adopt ReleaseKit for shared release validation, source publication, binary
|
||||||
|
builds, version information, and hosted delivery. Audita will follow its standard
|
||||||
|
workflow and supply product identity, delivery targets, release notes, and
|
||||||
|
necessary product-specific checks.
|
||||||
|
|
||||||
|
Better maintainability and less locally owned release-focused code outweigh any
|
||||||
|
small advantage from retaining Audita's custom release procedure. Changes to
|
||||||
|
that procedure are an accepted consequence of standardization.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
Retaining the custom procedure would preserve its exact behavior and local
|
||||||
|
control. It would also retain responsibility for duplicated mechanics, tests,
|
||||||
|
and maintenance across projects. The shared implementation offers greater
|
||||||
|
long-term value.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Audita's release integration becomes smaller, with common mechanics and their
|
||||||
|
tests maintained upstream. Audita benefits from shared fixes and takes on
|
||||||
|
ReleaseKit dependency maintenance.
|
||||||
|
|
||||||
|
Adoption requires updates to release tooling, CI wiring, documentation, and
|
||||||
|
runtime version reporting. The [release policy](../policy/release.md) continues
|
||||||
|
to govern compatibility and release completion; the
|
||||||
|
[release procedure](../release.md) will describe the adopted workflow.
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
# Architectural Decision Records
|
# Architectural Decision Records
|
||||||
|
|
||||||
No ADRs have been recorded here. Use the
|
## Accepted
|
||||||
|
|
||||||
|
- [ADR 0001: Adopt ReleaseKit for release management](0001-releasekit.md) —
|
||||||
|
accepted 2026-10-07.
|
||||||
|
|
||||||
|
Use the
|
||||||
[documentation policy's ADR lifecycle](../policy/documentation.md#architecture-decision-records)
|
[documentation policy's ADR lifecycle](../policy/documentation.md#architecture-decision-records)
|
||||||
for significant decisions. Current architecture belongs in the
|
for significant decisions. Current architecture belongs in the
|
||||||
[architecture policy](../policy/architecture.md); implementation status belongs in
|
[architecture policy](../policy/architecture.md); implementation status belongs in
|
||||||
|
|||||||
39
docs/cli.md
39
docs/cli.md
@@ -11,6 +11,7 @@ the [migration guide](migrations/v2.md) maps retired inputs.
|
|||||||
audita process <transcript.json> --glossary <glossary.yml> [flags]
|
audita process <transcript.json> --glossary <glossary.yml> [flags]
|
||||||
audita config validate --config <config.yml> [flags]
|
audita config validate --config <config.yml> [flags]
|
||||||
audita config print-effective [flags]
|
audita config print-effective [flags]
|
||||||
|
audita version
|
||||||
```
|
```
|
||||||
|
|
||||||
Root invocation, `--help`, `-h`, and command help succeed without loading a config
|
Root invocation, `--help`, `-h`, and command help succeed without loading a config
|
||||||
@@ -26,6 +27,16 @@ Missing system candidates are skipped; inaccessible selected files are errors.
|
|||||||
No file means a configuration error. `AUDITA_CONFIG` is ignored. The selected
|
No file means a configuration error. `AUDITA_CONFIG` is ignored. The selected
|
||||||
overlay is loaded beside the main file, not used to locate profile files.
|
overlay is loaded beside the main file, not used to locate profile files.
|
||||||
|
|
||||||
|
`audita version` prints `audita <version>` and a newline to stdout, with no
|
||||||
|
application configuration or provider setup. It returns `0` on success with
|
||||||
|
empty stderr. `help version`, `version -h`, and `version --help` print command
|
||||||
|
usage to stdout and return `0`. Help follows standard flag parsing and can
|
||||||
|
short-circuit trailing arguments. Other invalid invocations return `2` with a
|
||||||
|
usage error on stderr. The command uses ReleaseKit's public
|
||||||
|
`buildinfo.Version()` accessor. It reports an injected release identifier when
|
||||||
|
present, otherwise the executable's main-module version when available and not
|
||||||
|
`(devel)`, and `dev` when neither source provides a release identifier.
|
||||||
|
|
||||||
## Setting overrides
|
## Setting overrides
|
||||||
|
|
||||||
Only flags actually supplied override file or overlay settings. `--modules ''`
|
Only flags actually supplied override file or overlay settings. `--modules ''`
|
||||||
@@ -48,12 +59,17 @@ validation. The shared setting flags are:
|
|||||||
An omitted or empty output value selects stdout. Nonempty output filenames are
|
An omitted or empty output value selects stdout. Nonempty output filenames are
|
||||||
used literally, including whitespace.
|
used literally, including whitespace.
|
||||||
Reporting defaults to enabled. `--reporting=false` disables all three ordinary
|
Reporting defaults to enabled. `--reporting=false` disables all three ordinary
|
||||||
reports; an explicit false overrides a true file or overlay setting. With file
|
reports; an explicit false overrides a true file or overlay setting. For stdout
|
||||||
output, reports go beside the transcript and `--report-dir` has no effect. With
|
output, reports use the effective `reporting.stdout_dir`, which defaults to the
|
||||||
stdout output, `--report-dir` selects the report directory, defaulting to the
|
invocation working directory. For file output, they go beside the transcript
|
||||||
invocation working directory. `--debug` separately enables retained detailed
|
unless `--report-dir` is explicitly supplied. The flag selects the report
|
||||||
artifacts under `--work-dir`. Configuration inspection does not create or probe
|
directory for either transcript destination; a config-file or overlay value for
|
||||||
these output directories.
|
`reporting.stdout_dir` alone affects stdout output only. A selected report
|
||||||
|
directory must be nonblank when reporting is enabled. With reporting disabled,
|
||||||
|
the report destination is inactive and is not probed. `--debug` separately
|
||||||
|
enables retained detailed artifacts under `--work-dir`; selecting `--work-dir`
|
||||||
|
alone does not enable capture. Configuration inspection does not create or
|
||||||
|
probe these output directories.
|
||||||
Per-role model, base URL, key, timeout, retry, and concurrency flags have been
|
Per-role model, base URL, key, timeout, retry, and concurrency flags have been
|
||||||
removed. Models and generation controls belong to PromptKit profiles, backend
|
removed. Models and generation controls belong to PromptKit profiles, backend
|
||||||
credentials and capacity to configuration, and retry/deadline settings to the
|
credentials and capacity to configuration, and retry/deadline settings to the
|
||||||
@@ -69,9 +85,14 @@ YAML summary of selected ID, backend, model, sampling controls, per-attempt
|
|||||||
timeout, service tier, and reasoning effort for each role. It omits credentials,
|
timeout, service tier, and reasoning effort for each role. It omits credentials,
|
||||||
endpoints, and arbitrary extra provider parameters. Explain writes a YAML
|
endpoints, and arbitrary extra provider parameters. Explain writes a YAML
|
||||||
sequence of field paths, source origins, and recorded derivations without
|
sequence of field paths, source origins, and recorded derivations without
|
||||||
resolved setting values. These views share the same settings and profile
|
resolved setting values. An inherited field may also include an `inheritance`
|
||||||
validation path as `process`. Unknown views are usage errors. The effective
|
sequence; each step identifies `from`, `to`, and `reference` paths as JSON
|
||||||
settings YAML is for inspection and is not guaranteed reloadable.
|
pointers. These fields describe the effective inheritance chain without
|
||||||
|
including configuration values. A direct child override reports its own source
|
||||||
|
instead. Debug configuration origins use the same explanation shape. These views
|
||||||
|
share the same settings and profile validation path as `process`. Unknown views
|
||||||
|
are usage errors. The effective settings YAML is for inspection and is not
|
||||||
|
guaranteed reloadable.
|
||||||
|
|
||||||
For `process`, `--output` writes the corrected transcript to a file and leaves
|
For `process`, `--output` writes the corrected transcript to a file and leaves
|
||||||
stdout empty on success. Without it, stdout carries transcript JSON only on
|
stdout empty on success. Without it, stdout carries transcript JSON only on
|
||||||
|
|||||||
136
docs/config.md
136
docs/config.md
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
This reference describes the version-2 configuration loader in `internal/core/config`.
|
This reference describes the version-2 configuration loader in `internal/core/config`.
|
||||||
Use the maintained [local](../examples/minimal-config.yml) and
|
Use the maintained [local](../examples/minimal-config.yml) and
|
||||||
[service](../examples/production-config.yml) examples with their PromptKit
|
[service](../examples/production-config.yml) examples with their separately
|
||||||
[profiles](../examples/profiles/) and optional [testing overlay](../examples/overlays/testing.yml).
|
selected [local profiles](../examples/profiles/local/) and
|
||||||
|
[service profiles](../examples/profiles/service/), plus the optional
|
||||||
|
[testing overlay](../examples/overlays/testing.yml).
|
||||||
|
The [ConfigKit inheritance example](../examples/configkit-inheritance.yml)
|
||||||
|
shows a local child backend reusing and overriding sibling settings.
|
||||||
Choose real models for your endpoint; a testing profile makes no cost or quality
|
Choose real models for your endpoint; a testing profile makes no cost or quality
|
||||||
guarantee. See the [migration guide](migrations/v2.md) for retired inputs.
|
guarantee. See the [migration guide](migrations/v2.md) for retired inputs.
|
||||||
|
|
||||||
@@ -20,26 +24,123 @@ and secret-directory bootstrap are disabled. Unknown fields and missing, null,
|
|||||||
or non-integer `version: 2` are errors. Version-1 files require migration; no
|
or non-integer `version: 2` are errors. Version-1 files require migration; no
|
||||||
compatibility loader or implicit environment configuration remains.
|
compatibility loader or implicit environment configuration remains.
|
||||||
|
|
||||||
|
After overlay and explicit CLI override precedence is applied, ConfigKit resolves
|
||||||
|
native sibling inheritance before typed decoding. Any input mapping may use
|
||||||
|
`extends: <sibling-name>`; the child keeps its key, inherits parent settings, and
|
||||||
|
overrides them with its own settings. Selected overlay and CLI values therefore
|
||||||
|
participate in inheritance at their normal precedence. The generic syntax and
|
||||||
|
resolution rules are defined by ConfigKit v0.7.1's
|
||||||
|
[sibling inheritance contract](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.7.1/docs/config.md#sibling-inheritance).
|
||||||
|
|
||||||
|
`extends` is reserved structural input in base files, imports, overlays, and
|
||||||
|
other application input mappings, including mapping values supplied by explicit
|
||||||
|
CLI setting overrides. ConfigKit consumes it before typed decoding. A previously
|
||||||
|
valid backend whose ID is literally `extends` cannot be represented under this
|
||||||
|
rule. Rename that backend to a non-conflicting ID and update every PromptKit
|
||||||
|
profile that selects it. This compatibility impact means the upgrade does not
|
||||||
|
promise that every previously valid configuration remains valid.
|
||||||
|
|
||||||
|
Inherited values count as explicit input, including zero, false, empty, and
|
||||||
|
null. Existing field rules still apply: for example, inherited backend
|
||||||
|
concurrency must be positive, while an inherited queue capacity of zero is
|
||||||
|
valid. Parent backends undergo the same decoding and application validation as
|
||||||
|
other backends, whether or not a selected profile uses them. A child
|
||||||
|
`credential: null` removes the child's inherited credential; it does not remove
|
||||||
|
the parent's credential reference, which remains subject to ordinary secret
|
||||||
|
resolution.
|
||||||
|
|
||||||
Validation runs after overlays, explicit overrides, and derived settings have
|
Validation runs after overlays, explicit overrides, and derived settings have
|
||||||
resolved. An override can therefore repair an invalid lower-precedence setting
|
resolved. An override can therefore repair an invalid lower-precedence setting
|
||||||
when that setting can be decoded. ConfigKit retains field origins and derivations
|
when that setting can be decoded. ConfigKit retains field origins and derivations
|
||||||
for effective-configuration inspection. Its effective YAML redacts resolved
|
for effective-configuration inspection. Its effective YAML redacts resolved
|
||||||
credentials.
|
credentials.
|
||||||
|
|
||||||
Profile files are resolved against the selected main file's directory, or may
|
### PromptKit profiles and operator prompts
|
||||||
use an absolute `profiles_dir`. Both selected profiles must exist and select a
|
|
||||||
registered backend. Their effective endpoints must match that backend's endpoint
|
`profiles_dir` selects a PromptKit configuration root, resolved against the
|
||||||
after surrounding whitespace and trailing slashes are removed. Profiles cannot
|
selected main file's directory unless it is absolute. The root may contain
|
||||||
supply a competing `api_key_env` lookup. The backend credential supplies the
|
keyed `profiles`, `backends`, and `prompts` definitions; it is not limited to
|
||||||
runtime key when required; inspection makes no provider call. The
|
profile-only files. IDs come from literal YAML mapping keys, never filenames.
|
||||||
[CLI inspection views](cli.md#streams-and-exit-status) show selected controls and
|
Multiple definitions may share a document, and a document may contribute to any
|
||||||
field provenance separately from ConfigKit's redacted settings YAML.
|
of the supported mappings. Directory discovery is recursive, but discovery
|
||||||
|
order does not establish field precedence. Contributions within each source
|
||||||
|
group must compose without conflicting values.
|
||||||
|
|
||||||
|
PromptKit assembles sources in this order: its selected catalogs and owned
|
||||||
|
defaults, Audita's embedded prompt/schema inputs, operator documents under
|
||||||
|
`profiles_dir`, then the application backend mapping. The application mapping
|
||||||
|
owns each configured endpoint, concurrency limit, queue capacity, and credential
|
||||||
|
environment lookup. Other supported provider fields can be contributed by the
|
||||||
|
operator documents. Imports remain disabled. The current maintained examples
|
||||||
|
select separate roots: [local](../examples/profiles/local/) and
|
||||||
|
[service](../examples/profiles/service/), so definitions for one backend do not
|
||||||
|
enter the other example's final configuration.
|
||||||
|
|
||||||
|
Both selected roles must resolve to an existing profile, registered backend,
|
||||||
|
nonblank effective model, and nonblank endpoint. The normalized effective
|
||||||
|
endpoint must match the application backend endpoint. A selected profile cannot
|
||||||
|
introduce a competing `api_key_env` lookup; the application backend credential
|
||||||
|
supplies the runtime key when required. Other unselected profiles may be
|
||||||
|
partial, but every final backend, profile, and prompt definition is validated
|
||||||
|
when the engine is constructed. A malformed or conflicting unused definition
|
||||||
|
can therefore make the selected root invalid. A missing selected root is an
|
||||||
|
operator configuration error; an empty root may contribute no definitions.
|
||||||
|
|
||||||
|
Profile IDs and parent IDs are exact, literal keys. PromptKit profiles can use
|
||||||
|
native `extends` inheritance; child fields override inherited fields, while
|
||||||
|
unmentioned controls survive changes to model or backend. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
profiles:
|
||||||
|
"shared~base":
|
||||||
|
backend: service
|
||||||
|
model: example-model
|
||||||
|
temperature: 0.4
|
||||||
|
top_p: 0.8
|
||||||
|
"proposal fast":
|
||||||
|
extends: "shared~base"
|
||||||
|
model: faster-model
|
||||||
|
temperature: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Presence matters. Explicit `temperature: 0` is sent as zero; omission leaves it
|
||||||
|
unspecified; null suppresses an inherited temperature. Other fields have their
|
||||||
|
own native null and omission rules. Provider `extra_params` maps merge
|
||||||
|
recursively, and a null provider value is sent as JSON null. See the pinned
|
||||||
|
[PromptKit format reference](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.14.0/docs/formats.md)
|
||||||
|
for the complete field semantics.
|
||||||
|
|
||||||
|
Operator documents can also override an embedded prompt/version, including its
|
||||||
|
messages and definition-level output settings. Shared variables supplied by the
|
||||||
|
adapter are rendered only when an effective message references them; Audita does
|
||||||
|
not append omitted shared text. Audita's per-call schemas, structural repair
|
||||||
|
budget, and downstream domain validators remain in force. The engine captures
|
||||||
|
effective definitions and message-file contents when constructed, so edits are
|
||||||
|
observed only by a newly constructed runtime.
|
||||||
|
|
||||||
|
### Migrating PromptKit profile documents
|
||||||
|
|
||||||
|
For the PromptKit v0.11 format, wrap each profile's fields under
|
||||||
|
`profiles: {<literal-id>: ...}`, remove inner `id`, and replace
|
||||||
|
`base_profile` with `extends`. Profile and prompt IDs are mapping keys, so they
|
||||||
|
need not match source filenames. Review zeros, omitted values, and nulls against
|
||||||
|
the native presence rules before deploying. Select a root whose final
|
||||||
|
definitions have valid backend references; eager validation applies even to
|
||||||
|
unselected definitions. See the complete maintained [local](../examples/minimal-config.yml)
|
||||||
|
and [service](../examples/production-config.yml) examples and the pinned
|
||||||
|
[consumer migration guide](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.14.0/docs/consumers/pkg-promptkit.md#migrate-from-the-previous-configuration-api).
|
||||||
|
This incompatible profile-format migration is intended for an Audita major
|
||||||
|
release; no automatic file conversion is provided.
|
||||||
|
|
||||||
|
The [CLI inspection views](cli.md#streams-and-exit-status) show selected
|
||||||
|
controls and field provenance separately from ConfigKit's redacted settings
|
||||||
|
YAML. Inspection resolves both roles without provider calls.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
| Field | Default | Rule |
|
| Field | Default | Rule |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `profiles_dir` | `profiles` | Nonempty; profile files are resolved relative to the selected main file. |
|
| `profiles_dir` | `profiles` | Nonempty PromptKit document root; resolved relative to the selected main file, or absolute. |
|
||||||
| `backends.<id>.endpoint` | None | Required nonempty endpoint for each backend. At least one backend is required. |
|
| `backends.<id>.endpoint` | None | Required nonempty endpoint for each backend. At least one backend is required. |
|
||||||
| `backends.<id>.concurrency` | `1` | Positive finite capacity; shared by both roles using that backend. |
|
| `backends.<id>.concurrency` | `1` | Positive finite capacity; shared by both roles using that backend. |
|
||||||
| `backends.<id>.queue_capacity` | `1024` | Nonnegative; zero disables waiting admission. |
|
| `backends.<id>.queue_capacity` | `1024` | Nonnegative; zero disables waiting admission. |
|
||||||
@@ -62,17 +163,20 @@ field provenance separately from ConfigKit's redacted settings YAML.
|
|||||||
| `thresholds.glossary`, `.homophones`, `.spoken_word`, `.grammar` | `0.8` each | Confidence thresholds in `[0, 1]`. |
|
| `thresholds.glossary`, `.homophones`, `.spoken_word`, `.grammar` | `0.8` each | Confidence thresholds in `[0, 1]`. |
|
||||||
| `context.description` | Empty | Trimmed prompt context, at most 500 bytes. |
|
| `context.description` | Empty | Trimmed prompt context, at most 500 bytes. |
|
||||||
| `reporting.enabled` | `true` | Write the ordinary three-file report bundle when processing. |
|
| `reporting.enabled` | `true` | Write the ordinary three-file report bundle when processing. |
|
||||||
| `reporting.stdout_dir` | `.` | Report directory for stdout transcript output; ignored for file output or disabled reporting. |
|
| `reporting.stdout_dir` | `.` | Report directory for stdout transcript output; a `--report-dir` flag can select reports for either transcript destination. |
|
||||||
| `diagnostics.debug` | `false` | Persist detailed debug artifacts when processing. |
|
| `diagnostics.debug` | `false` | Persist detailed debug artifacts when processing. |
|
||||||
| `diagnostics.work_dir` | `/tmp/audita` | Parent of the retained debug run directory; required only when debug is enabled. |
|
| `diagnostics.work_dir` | `/tmp/audita` | Parent of the retained debug run directory; required only when debug is enabled. |
|
||||||
|
|
||||||
Module keys select correction modules; validator chains remain built into them.
|
Module keys select correction modules; validator chains remain built into them.
|
||||||
The loader preserves module order and repetitions. Normalization time settings
|
The loader preserves module order and repetitions. Normalization time settings
|
||||||
accept numeric seconds only. The operation timeout uses a duration string.
|
accept numeric seconds only. The operation timeout uses a duration string.
|
||||||
`reporting.stdout_dir` must be nonblank only for reporting-enabled stdout runs;
|
`reporting.stdout_dir` must be nonblank when it supplies the active report
|
||||||
`diagnostics.work_dir` must be nonblank only with debug enabled. These path
|
destination: for stdout output, or for file output when `--report-dir` is
|
||||||
requirements are checked when choosing process destinations, not during
|
explicitly supplied. File output without that flag uses the transcript's parent,
|
||||||
configuration inspection.
|
so the configured value is inactive. Reporting-disabled runs do not select a
|
||||||
|
report directory. `diagnostics.work_dir` must be nonblank only with debug
|
||||||
|
enabled. These path requirements are checked when choosing process destinations,
|
||||||
|
not during configuration inspection.
|
||||||
|
|
||||||
## Credentials
|
## Credentials
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ ownership. Commit discipline and agent Git restrictions are in the
|
|||||||
|
|
||||||
## Baseline Validation
|
## Baseline Validation
|
||||||
|
|
||||||
Use Go 1.25.5 or newer. From the repository root:
|
Use Go 1.26.0 or newer for source builds. The selected release toolchain is
|
||||||
|
documented in the [release procedure](release.md#prerequisites). From the
|
||||||
|
repository root:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go build -o ./bin/audita ./cmd/audita
|
go build -o ./bin/audita ./cmd/audita
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ failures; explicit zero makes one attempt. `llm.repair_attempts` counts
|
|||||||
additional generations for schema-invalid model output. The operation timeout
|
additional generations for schema-invalid model output. The operation timeout
|
||||||
covers preparation, capacity waiting, retries, generation, and repair. Caller
|
covers preparation, capacity waiting, retries, generation, and repair. Caller
|
||||||
cancellation or an earlier deadline also applies. See [configuration](../config.md#settings)
|
cancellation or an earlier deadline also applies. See [configuration](../config.md#settings)
|
||||||
for defaults and [PromptKit's consumer contract](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.11.2/docs/consumers/pkg-promptkit.md)
|
for defaults and [PromptKit v0.14.0's consumer contract](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.14.0/docs/consumers/pkg-promptkit.md)
|
||||||
for the pinned protocol and retry details.
|
for the pinned protocol and retry details.
|
||||||
|
|
||||||
A model operation that exhausts structural repair, a live-parent local operation
|
A model operation that exhausts structural repair, a live-parent local operation
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ The [CLI reference](../cli.md) owns command syntax, streams, and exit semantics;
|
|||||||
Use an argument array with the transcript path and required glossary argument,
|
Use an argument array with the transcript path and required glossary argument,
|
||||||
following the [process examples](../cli.md#commands-and-help). For machine workflows,
|
following the [process examples](../cli.md#commands-and-help). For machine workflows,
|
||||||
select an explicit transcript file destination and a configuration path. Reports
|
select an explicit transcript file destination and a configuration path. Reports
|
||||||
then go beside the transcript. For stdout output, select `--report-dir` or use
|
go beside the transcript unless an explicit `--report-dir` selects another
|
||||||
the invocation working directory. Debug is independent and off by default.
|
directory. For stdout output, reports use the effective `reporting.stdout_dir`
|
||||||
Supplying a configuration path also
|
(defaulting to the invocation working directory), including a `--report-dir`
|
||||||
makes file selection predictable; only explicitly referenced credential variables
|
override. A config-file or overlay value for `reporting.stdout_dir` alone does
|
||||||
are read from the environment.
|
not redirect file-output reports. `--work-dir` selects a debug root, while
|
||||||
|
configuration or `--debug` enables capture; the path flag alone does not.
|
||||||
|
Supplying a configuration path also makes file selection predictable; only
|
||||||
|
explicitly referenced credential variables are read from the environment.
|
||||||
|
|
||||||
Resolve the executable deliberately and set the child's working directory so
|
Resolve the executable deliberately and set the child's working directory so
|
||||||
relative input/output paths and reported artifact paths have a known base.
|
relative input/output paths and reported artifact paths have a known base.
|
||||||
@@ -28,7 +31,8 @@ Consume transcript/report data through the
|
|||||||
[output-file contract](output-files.md), and treat stderr as human-readable detail.
|
[output-file contract](output-files.md), and treat stderr as human-readable detail.
|
||||||
The three reports may be absent after early configuration or destination
|
The three reports may be absent after early configuration or destination
|
||||||
failures. After initialization, failed reports can describe invalid input or
|
failures. After initialization, failed reports can describe invalid input or
|
||||||
partial processing. Exit `0` can include recoverable model errors and applied
|
partial processing and use the same selected report directory as successful
|
||||||
|
runs. Exit `0` can include recoverable model errors and applied
|
||||||
corrections; inspect `errors.yml` and `recoveries.yml` for those outcomes. A
|
corrections; inspect `errors.yml` and `recoveries.yml` for those outcomes. A
|
||||||
transcript may have been written before a later required report failure, so
|
transcript may have been written before a later required report failure, so
|
||||||
the file alone does not prove completion.
|
the file alone does not prove completion.
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ CLI wiring, and maintained examples. Definitions of behavior belong to the
|
|||||||
for the changed behavior.
|
for the changed behavior.
|
||||||
4. Update the CLI reference and affected integration guidance.
|
4. Update the CLI reference and affected integration guidance.
|
||||||
|
|
||||||
|
The `version` route uses ReleaseKit's public `buildinfo.Version()` accessor and
|
||||||
|
stays independent of application runtime assembly. Preserve its behavior from
|
||||||
|
the [CLI contract](../cli.md#commands-and-help) when changing the command; the
|
||||||
|
adoption decision is recorded in [ADR 0001](../adr/0001-releasekit.md).
|
||||||
|
|
||||||
## Validating Examples
|
## Validating Examples
|
||||||
|
|
||||||
Keep complete copyable files in [examples](../../examples/) and link to them from
|
Keep complete copyable files in [examples](../../examples/) and link to them from
|
||||||
@@ -35,11 +40,18 @@ From the repository root, validate the maintained configuration examples:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
go run ./cmd/audita config validate --config examples/minimal-config.yml
|
go run ./cmd/audita config validate --config examples/minimal-config.yml
|
||||||
|
go run ./cmd/audita config validate --config examples/configkit-inheritance.yml
|
||||||
AUDITA_SERVICE_KEY=synthetic-example go run ./cmd/audita config validate --config examples/production-config.yml
|
AUDITA_SERVICE_KEY=synthetic-example go run ./cmd/audita config validate --config examples/production-config.yml
|
||||||
AUDITA_SERVICE_KEY=synthetic-example go run ./cmd/audita config validate --config examples/production-config.yml --overlay testing
|
AUDITA_SERVICE_KEY=synthetic-example go run ./cmd/audita config validate --config examples/production-config.yml --overlay testing
|
||||||
go run ./cmd/audita config print-effective --config examples/minimal-config.yml --view profiles
|
go run ./cmd/audita config print-effective --config examples/minimal-config.yml --view profiles
|
||||||
|
go run ./cmd/audita config print-effective --config examples/configkit-inheritance.yml --view explain
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The local and ConfigKit inheritance files select `profiles/local`; the service
|
||||||
|
file selects `profiles/service`, including the service testing profiles. Keep
|
||||||
|
these roots separate because PromptKit validates every final definition in a
|
||||||
|
selected root, including definitions that are not selected for a role.
|
||||||
|
|
||||||
Only referenced credentials read environment values; use a controlled synthetic
|
Only referenced credentials read environment values; use a controlled synthetic
|
||||||
value when validating the service example. Check affected CLI examples against the
|
value when validating the service example. Check affected CLI examples against the
|
||||||
parser and use schema tests for maintained transcript/glossary inputs. Example
|
parser and use schema tests for maintained transcript/glossary inputs. Example
|
||||||
|
|||||||
@@ -2,11 +2,28 @@
|
|||||||
|
|
||||||
## Client Boundary
|
## Client Boundary
|
||||||
|
|
||||||
`llm.NewRuntime` assembles one PromptKit engine for both roles. It loads embedded
|
`llm.NewRuntime` assembles one PromptKit engine for both roles. PromptKit's
|
||||||
prompt and schema assets, registers application backends, and inspects the
|
catalogs and defaults are followed by Audita's embedded prompt and schema
|
||||||
selected proposal and validation profiles offline. A role-bound `llm.RoleClient`
|
inputs, operator documents from `profiles_dir`, and the highest-priority
|
||||||
implements `structuredoutput.Client`; proposal and validator contracts alias that
|
application backend mapping. Audita owns backend endpoints, capacity settings,
|
||||||
interface. Both roles share engine-local backend capacity.
|
and resolved credentials; native PromptKit layering owns profile inheritance,
|
||||||
|
prompt overrides, and provider-specific fields. Imports are disabled. The
|
||||||
|
runtime inspects both selected roles offline, requiring complete model and
|
||||||
|
endpoint values and rejecting competing profile credential lookups. A
|
||||||
|
role-bound `llm.RoleClient` implements `structuredoutput.Client`; proposal and
|
||||||
|
validator contracts alias that interface. Both roles share engine-local backend
|
||||||
|
capacity.
|
||||||
|
|
||||||
|
Successful construction captures the final definitions and referenced message
|
||||||
|
contents. The runtime does not reload operator documents or message files during
|
||||||
|
calls. If final engine construction fails, an embedded-only diagnostic
|
||||||
|
construction distinguishes shipped asset failures from invalid operator input.
|
||||||
|
An asset failure remains an asset error; when embedded construction succeeds,
|
||||||
|
the original failure remains a configuration error. The diagnostic engine
|
||||||
|
never replaces or executes the requested configuration, and caller cancellation
|
||||||
|
skips this path. The captured inspection also supplies an internal source
|
||||||
|
ledger: all loaded host YAML plus effective, source-relative host
|
||||||
|
`content_file` resources are tracked for output-collision checks.
|
||||||
|
|
||||||
For each logical call, the adapter starts an operation context and a diagnostic
|
For each logical call, the adapter starts an operation context and a diagnostic
|
||||||
record before preparation. It supplies the reserved hardening text, the selected
|
record before preparation. It supplies the reserved hardening text, the selected
|
||||||
@@ -24,6 +41,10 @@ Counts distinguish provider attempts, transient retries, and structural
|
|||||||
repairs, including on errors. Secrets from all configured backends, including unselected
|
repairs, including on errors. Secrets from all configured backends, including unselected
|
||||||
ones, are available to the diagnostics redaction boundary.
|
ones, are available to the diagnostics redaction boundary.
|
||||||
|
|
||||||
|
CLI explain output and debug configuration origins share a value-free projection
|
||||||
|
of effective field origins. The [CLI contract](../cli.md) defines its optional
|
||||||
|
inheritance evidence fields.
|
||||||
|
|
||||||
## Orchestration
|
## Orchestration
|
||||||
|
|
||||||
CLI processing supplies two role clients from the same engine to the runner.
|
CLI processing supplies two role clients from the same engine to the runner.
|
||||||
|
|||||||
@@ -11,18 +11,27 @@
|
|||||||
- `validators.meaning_reversal_review` (`v1`)
|
- `validators.meaning_reversal_review` (`v1`)
|
||||||
- `validators.editorial_review` (`v1`)
|
- `validators.editorial_review` (`v1`)
|
||||||
|
|
||||||
PromptKit reads the embedded definitions and renders their system and user
|
PromptKit reads the embedded definitions as shipped defaults and renders their
|
||||||
messages from named string variables. The role-bound adapter supplies
|
messages from named string variables. Operator documents in `profiles_dir` may
|
||||||
|
contribute native keyed prompt definitions and override an embedded
|
||||||
|
prompt/version, including its messages and definition-level output settings.
|
||||||
|
The effective operator text is captured when the runtime is constructed; the
|
||||||
|
embedded assets remain the source of Audita's shipped task instructions. The
|
||||||
|
role-bound adapter supplies
|
||||||
`HardeningPolicy` alongside `ProposalContract` and `ValidationContract` from
|
`HardeningPolicy` alongside `ProposalContract` and `ValidationContract` from
|
||||||
embedded fragments under `assets/shared`, after caller variables so callers cannot replace those
|
embedded fragments under `assets/shared`, after caller variables so callers
|
||||||
instructions. Common literal-span and identity guidance lives in the proposal
|
cannot replace those instructions. Common literal-span and identity guidance
|
||||||
fragment; shared candidate-reference, context, and confidence guidance lives in
|
lives in the proposal fragment; shared candidate-reference, context, and
|
||||||
the validation fragment. Module-specific policies and examples remain in YAML.
|
confidence guidance lives in the validation fragment. Module-specific policies
|
||||||
Proposal prompts receive the section JSON, glossary JSON where used, and transcript description block.
|
and examples remain in YAML. Proposal prompts receive the section JSON, glossary
|
||||||
Validator prompts receive the correction payload and description block.
|
JSON where used, and transcript description block. Validator prompts receive
|
||||||
The prompt assets retain the prior task instructions and output-schema
|
the correction payload and description block.
|
||||||
association. The role-bound runtime supplies the complete JSON Schema output contract
|
The embedded prompt assets retain the shipped task instructions and
|
||||||
and repair count; the definition does not own an operator-facing repair default.
|
output-schema association. An operator override controls which template
|
||||||
|
variables its messages reference: supplying a shared variable does not append
|
||||||
|
its text when the effective message omits that reference. The role-bound runtime
|
||||||
|
supplies the complete JSON Schema output contract and repair count; the
|
||||||
|
definition does not own an operator-facing repair default.
|
||||||
The correction-set schema requires a string replacement and accepts an empty
|
The correction-set schema requires a string replacement and accepts an empty
|
||||||
string. Proposal guidance uses it for eligible span deletion and permits literal
|
string. Proposal guidance uses it for eligible span deletion and permits literal
|
||||||
spacing edits; validator guidance evaluates the full before/after segment under
|
spacing edits; validator guidance evaluates the full before/after segment under
|
||||||
|
|||||||
@@ -17,23 +17,32 @@ settings, and the [output contract](integrations/output-files.md) owns schemas.
|
|||||||
## Required Output Files
|
## Required Output Files
|
||||||
|
|
||||||
Reporting is enabled by default. For file transcript output, `errors.yml`,
|
Reporting is enabled by default. For file transcript output, `errors.yml`,
|
||||||
`recoveries.yml`, and `report.md` go in the transcript output's parent directory.
|
`recoveries.yml`, and `report.md` go in the transcript output's parent directory
|
||||||
For stdout transcript output, they go in `reporting.stdout_dir` (default `.`),
|
unless `--report-dir` is explicitly supplied. For stdout transcript output, they
|
||||||
overridden by `--report-dir`. `--reporting=false` suppresses all three reports
|
go in the effective `reporting.stdout_dir` (default `.`), including a
|
||||||
and removes their path requirements. It does not suppress the stderr summary or
|
`--report-dir` override. A config-file or overlay value for
|
||||||
|
`reporting.stdout_dir` alone does not redirect file-output reports. An active
|
||||||
|
selected report directory must be nonblank. `--reporting=false` suppresses all
|
||||||
|
three reports and leaves their destination inactive, so it is not probed merely
|
||||||
|
because `--report-dir` was supplied. It does not suppress the stderr summary or
|
||||||
fatal errors. Stdout contains only transcript JSON; file output leaves it empty.
|
fatal errors. Stdout contains only transcript JSON; file output leaves it empty.
|
||||||
|
|
||||||
Existing report directories and parents for regular transcript files must be
|
Existing report directories and parents for regular transcript files must be
|
||||||
writable. Audita prevents report writes from overwriting transcript, glossary,
|
writable. Audita prevents report writes from overwriting transcript, glossary,
|
||||||
loaded configuration, or loaded profile sources, and rejects conflicting output
|
loaded application configuration, host YAML discovered under `profiles_dir`,
|
||||||
destinations. The final transcript may intentionally replace its own input after
|
or host message files referenced by effective PromptKit `content_file` fields,
|
||||||
successful processing; other input/configuration/profile sources remain protected.
|
and rejects conflicting output destinations. Source paths come from the
|
||||||
|
runtime's captured configuration; unrelated files in the selected directory
|
||||||
|
remain eligible output destinations. The final transcript may intentionally
|
||||||
|
replace its own input after successful processing; other input/configuration/
|
||||||
|
profile sources remain protected.
|
||||||
Preparation never truncates an existing transcript. Device and pipe destinations
|
Preparation never truncates an existing transcript. Device and pipe destinations
|
||||||
(such as `/dev/null` and named pipes) receive ordinary writes at publication time;
|
(such as `/dev/null` and named pipes) receive ordinary writes at publication time;
|
||||||
they are not opened during preflight or replaced with regular files. A pipe write
|
they are not opened during preflight or replaced with regular files. A pipe write
|
||||||
can wait for its reader, and stream write errors are fatal. With reporting enabled,
|
can wait for its reader, and stream write errors are fatal. With reporting enabled,
|
||||||
reports still use the output path's parent; disable reporting when that placement
|
reports still use the output path's parent unless an explicit `--report-dir`
|
||||||
is unsuitable for a stream destination.
|
selects another directory; disable reporting when that placement is unsuitable
|
||||||
|
for a stream destination.
|
||||||
|
|
||||||
With reporting enabled, all three reports are initialized as `in_progress` before
|
With reporting enabled, all three reports are initialized as `in_progress` before
|
||||||
input processing. Output is encoded fully before publication. Each regular output
|
input processing. Output is encoded fully before publication. Each regular output
|
||||||
@@ -48,10 +57,12 @@ finalized where possible. An uncatchable termination can leave `in_progress`.
|
|||||||
|
|
||||||
## Diagnostics Directory
|
## Diagnostics Directory
|
||||||
|
|
||||||
`--debug` enables detailed evidence independently of reporting. One directory
|
`--debug` or `diagnostics.debug` enables detailed evidence independently of
|
||||||
named `run-<id>` is created under `diagnostics.work_dir` (default `/tmp/audita`)
|
reporting. One directory named `run-<id>` is created under
|
||||||
and retained on success or failure. Without debug, no directory or payload
|
`diagnostics.work_dir` (default `/tmp/audita`), which `--work-dir` can override,
|
||||||
files are created. The parent is created when needed only for debug. Failure to
|
and retained on success or failure. Selecting `--work-dir` alone does not enable
|
||||||
|
capture. Without debug, no directory or payload files are created. The parent
|
||||||
|
is created when needed only for debug. Failure to
|
||||||
write optional debug evidence prints a redacted stderr diagnostic but does not
|
write optional debug evidence prints a redacted stderr diagnostic but does not
|
||||||
change correction decisions or the exit status.
|
change correction decisions or the exit status.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
## Standard Repository Policies
|
## Standard Repository Policies
|
||||||
|
|
||||||
Material: All documents under `docs/policy/`, root `AGENTS.md`, and the Policy Reading Guide in `docs/development.md`, adapted for this repository.
|
Material: All documents under `docs/policy/`, root `AGENTS.md`, and the Policy
|
||||||
|
Reading Guide in `docs/development.md`, adapted for this repository.
|
||||||
|
`docs/policy/release.md` incorporates revision
|
||||||
|
`e833faddf120c789a9b45348edaf75720de16348`; the remaining listed material
|
||||||
|
incorporates revision `d84a38ebab26f048cafbe59a2246700c35d29843`.
|
||||||
|
|
||||||
Copyright (C) 2026 Eric Rakestraw
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|
||||||
@@ -10,13 +14,17 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||||||
|
|
||||||
License and notices: [CC BY-SA 4.0](licenses/CC-BY-SA-4.0.txt) |
|
License and notices: [CC BY-SA 4.0](licenses/CC-BY-SA-4.0.txt) |
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/COPYRIGHT) |
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/COPYRIGHT) |
|
||||||
[LICENSE](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/LICENSE).
|
[LICENSE](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/LICENSE) |
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/COPYRIGHT) |
|
||||||
|
[LICENSE](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/LICENSE).
|
||||||
|
|
||||||
[Source](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843).
|
[Source](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843).
|
||||||
|
|
||||||
|
[Source](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348).
|
||||||
|
|
||||||
## ConfigKit
|
## ConfigKit
|
||||||
|
|
||||||
Selected module: `gitea.maximumdirect.net/eric/ConfigKit` at `v0.4.2`.
|
Selected module: `gitea.maximumdirect.net/eric/ConfigKit` at `v0.7.1`.
|
||||||
|
|
||||||
Copyright (C) 2026 Eric Rakestraw
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|
||||||
@@ -24,79 +32,93 @@ SPDX-License-Identifier: LGPL-3.0-or-later
|
|||||||
|
|
||||||
License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
|
License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
|
||||||
[GPL v3](licenses/GPL-3.0.txt) |
|
[GPL v3](licenses/GPL-3.0.txt) |
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/67c03690181c5af8dde6be325abac241dae6732f/COPYRIGHT).
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/6fffde4af8f31f99d3ee295c426eb3234cb0fb77/COPYRIGHT).
|
||||||
|
|
||||||
[Source](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/67c03690181c5af8dde6be325abac241dae6732f).
|
[Source](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/6fffde4af8f31f99d3ee295c426eb3234cb0fb77).
|
||||||
|
|
||||||
|
## ReleaseKit
|
||||||
|
|
||||||
|
Selected module: `gitea.maximumdirect.net/eric/ReleaseKit` at `v0.4.1`.
|
||||||
|
|
||||||
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
|
||||||
|
[GPL v3](licenses/GPL-3.0.txt) |
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/2d5a8110c61e31daf8698766114ae305d0e0e469/COPYRIGHT).
|
||||||
|
|
||||||
|
[Source](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/2d5a8110c61e31daf8698766114ae305d0e0e469).
|
||||||
|
|
||||||
## Go runtime and standard library
|
## Go runtime and standard library
|
||||||
|
|
||||||
Selected toolchain: Go 1.25.5 for the distributed Linux binaries.
|
Selected toolchain: Go 1.26.8 for the distributed Linux binaries.
|
||||||
|
|
||||||
Copyright 2009 The Go Authors.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
SPDX-License-Identifier: BSD-3-Clause
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
||||||
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/LICENSE) |
|
[LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/LICENSE) |
|
||||||
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/PATENTS).
|
[PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/PATENTS).
|
||||||
|
|
||||||
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9).
|
[Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
|
||||||
|
|
||||||
## Go vendored golang.org/x/crypto
|
## Go vendored golang.org/x/crypto
|
||||||
|
|
||||||
Material: `golang.org/x/crypto` at `v0.39.0` in the Go 1.25.5 standard library.
|
Material: `golang.org/x/crypto` at `v0.46.1-0.20251210140736-7dacc380ba00` in the Go 1.26.8 standard library.
|
||||||
|
|
||||||
Copyright 2009 The Go Authors.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
SPDX-License-Identifier: BSD-3-Clause
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
||||||
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/crypto/LICENSE) |
|
[LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/crypto/LICENSE) |
|
||||||
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/crypto/PATENTS).
|
[PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/crypto/PATENTS).
|
||||||
|
|
||||||
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9).
|
[Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
|
||||||
|
|
||||||
## Go vendored golang.org/x/net
|
## Go vendored golang.org/x/net
|
||||||
|
|
||||||
Material: `golang.org/x/net` at `v0.41.0` in the Go 1.25.5 standard library.
|
Material: `golang.org/x/net` at `v0.47.1-0.20260731170545-cb1d721f7fb3` in the Go 1.26.8 standard library.
|
||||||
|
|
||||||
Copyright 2009 The Go Authors.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
SPDX-License-Identifier: BSD-3-Clause
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
||||||
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/net/LICENSE) |
|
[LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/net/LICENSE) |
|
||||||
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/net/PATENTS).
|
[PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/net/PATENTS).
|
||||||
|
|
||||||
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9).
|
[Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
|
||||||
|
|
||||||
## Go vendored golang.org/x/sys
|
## Go vendored golang.org/x/sys
|
||||||
|
|
||||||
Material: `golang.org/x/sys` at `v0.33.0` in the Go 1.25.5 standard library.
|
Material: `golang.org/x/sys` at `v0.39.0` in the Go 1.26.8 standard library.
|
||||||
|
|
||||||
Copyright 2009 The Go Authors.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
SPDX-License-Identifier: BSD-3-Clause
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
||||||
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/sys/LICENSE) |
|
[LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/sys/LICENSE) |
|
||||||
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/sys/PATENTS).
|
[PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/sys/PATENTS).
|
||||||
|
|
||||||
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9).
|
[Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
|
||||||
|
|
||||||
## Go vendored golang.org/x/text
|
## Go vendored golang.org/x/text
|
||||||
|
|
||||||
Material: `golang.org/x/text` at `v0.26.0` in the Go 1.25.5 standard library.
|
Material: `golang.org/x/text` at `v0.32.0` in the Go 1.26.8 standard library.
|
||||||
|
|
||||||
Copyright 2009 The Go Authors.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
SPDX-License-Identifier: BSD-3-Clause
|
SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
|
||||||
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/text/LICENSE) |
|
[LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/text/LICENSE) |
|
||||||
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/text/PATENTS).
|
[PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/text/PATENTS).
|
||||||
|
|
||||||
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9).
|
[Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
|
||||||
|
|
||||||
## go-yaml v4
|
## go-yaml v4
|
||||||
|
|
||||||
@@ -142,7 +164,7 @@ License and notices: [Apache 2.0](licenses/Apache-2.0.txt).
|
|||||||
|
|
||||||
## PromptKit
|
## PromptKit
|
||||||
|
|
||||||
Selected module: `gitea.maximumdirect.net/eric/promptkit` at `v0.11.2`.
|
Selected module: `gitea.maximumdirect.net/eric/promptkit` at `v0.14.0`.
|
||||||
|
|
||||||
Copyright (C) 2026 Eric Rakestraw
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|
||||||
@@ -150,37 +172,37 @@ SPDX-License-Identifier: LGPL-3.0-or-later
|
|||||||
|
|
||||||
License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
|
License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
|
||||||
[GPL v3](licenses/GPL-3.0.txt) |
|
[GPL v3](licenses/GPL-3.0.txt) |
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit/src/commit/12d6d80024e80c552d122a076c22429bf6f2f6c2/COPYRIGHT).
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit/src/commit/143c6e9b3a675872117b0471e2ff8bcb25055a54/COPYRIGHT).
|
||||||
|
|
||||||
[Source](https://gitea.maximumdirect.net/eric/promptkit/src/commit/12d6d80024e80c552d122a076c22429bf6f2f6c2).
|
[Source](https://gitea.maximumdirect.net/eric/promptkit/src/commit/143c6e9b3a675872117b0471e2ff8bcb25055a54).
|
||||||
|
|
||||||
## Promptkit OpenRouter Catalog
|
## Promptkit OpenRouter Catalog
|
||||||
|
|
||||||
Selected module: `gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2` at `v2.20260926.0`.
|
Selected module: `gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3` at `v3.20260930.0`.
|
||||||
|
|
||||||
Copyright (C) 2026 Eric Rakestraw
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
License and notices: [MIT](licenses/MIT-Catalogs.txt) |
|
License and notices: [MIT](licenses/MIT-Catalogs.txt) |
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/commit/4f731d217e0bc39151c010f50fb5912706e7b6b3/COPYRIGHT) |
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/commit/f17d208cb7ad1fb043cda2266cd3011d11b40aa0/COPYRIGHT) |
|
||||||
[LICENSE](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/commit/4f731d217e0bc39151c010f50fb5912706e7b6b3/LICENSE).
|
[LICENSE](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/commit/f17d208cb7ad1fb043cda2266cd3011d11b40aa0/LICENSE).
|
||||||
|
|
||||||
[Source](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/commit/4f731d217e0bc39151c010f50fb5912706e7b6b3).
|
[Source](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/commit/f17d208cb7ad1fb043cda2266cd3011d11b40aa0).
|
||||||
|
|
||||||
## Promptkit Rakestrawhome Catalog
|
## Promptkit Rakestrawhome Catalog
|
||||||
|
|
||||||
Selected module: `gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2` at `v2.20260926.0`.
|
Selected module: `gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3` at `v3.20260930.0`.
|
||||||
|
|
||||||
Copyright (C) 2026 Eric Rakestraw
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
License and notices: [MIT](licenses/MIT-Catalogs.txt) |
|
License and notices: [MIT](licenses/MIT-Catalogs.txt) |
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/commit/48918a3ad39fd99843cda94a86133b1b3881d596/COPYRIGHT) |
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/commit/993443d88c451901f4c917fa00c204fd3b54d28d/COPYRIGHT) |
|
||||||
[LICENSE](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/commit/48918a3ad39fd99843cda94a86133b1b3881d596/LICENSE).
|
[LICENSE](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/commit/993443d88c451901f4c917fa00c204fd3b54d28d/LICENSE).
|
||||||
|
|
||||||
[Source](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/commit/48918a3ad39fd99843cda94a86133b1b3881d596).
|
[Source](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/commit/993443d88c451901f4c917fa00c204fd3b54d28d).
|
||||||
|
|
||||||
## yaml.v3
|
## yaml.v3
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Release Policy
|
# Release Policy
|
||||||
|
|
||||||
**Base Policy Version:** 1.4
|
**Base Policy Version:** 1.6
|
||||||
|
|
||||||
**Last Updated**: 2026-09-28
|
**Last Updated**: 2026-10-08
|
||||||
**Upstream Source:** [Standard Repository Policies release](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/release.md)
|
**Upstream Source:** [Standard Repository Policies release](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/docs/policy/release.md)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
@@ -11,6 +11,9 @@ This policy defines a proportionate workflow for validating and publishing an
|
|||||||
intended source revision under a fresh version. Releases should require little
|
intended source revision under a fresh version. Releases should require little
|
||||||
routine intervention, avoid repeated work, and preserve published contents.
|
routine intervention, avoid repeated work, and preserve published contents.
|
||||||
|
|
||||||
|
Adopt stronger verification or reproducibility requirements through an explicit
|
||||||
|
project-specific decision justified by the intended audience and support needs.
|
||||||
|
|
||||||
The repository-specific commands belong in `docs/release.md`. Historical release
|
The repository-specific commands belong in `docs/release.md`. Historical release
|
||||||
summaries belong in `docs/releases/`. The procedure must agree with the adopted
|
summaries belong in `docs/releases/`. The procedure must agree with the adopted
|
||||||
policy and link to it rather than introduce additional release requirements.
|
policy and link to it rather than introduce additional release requirements.
|
||||||
@@ -49,12 +52,17 @@ failures and optional investigations under artifact-delivery status.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Linux amd64 | Required production target |
|
| Linux amd64 | Required production target |
|
||||||
| Linux arm64 | Required production target |
|
| Linux arm64 | Required production target |
|
||||||
| macOS arm64 | Best effort for local development; failures do not block a release or Linux artifact publication |
|
| macOS arm64 | Best effort for local development; development failures do not block a release or Linux artifact publication |
|
||||||
|
|
||||||
Windows and macOS amd64 are unsupported. Neither their builds nor tests are
|
Windows and macOS amd64 are unsupported. Neither their builds nor tests are
|
||||||
release requirements. Native macOS testing and access to a Mac are not release
|
release requirements. Native macOS testing and access to a Mac are not release
|
||||||
prerequisites.
|
prerequisites.
|
||||||
|
|
||||||
|
Linux amd64 and arm64 remain the required production targets. Publication from
|
||||||
|
a host outside the selected target set must pass ReleaseKit's temporary native
|
||||||
|
version check. This check does not require access to a Mac or native tests on
|
||||||
|
every delivery target.
|
||||||
|
|
||||||
## Versioning And Compatibility
|
## Versioning And Compatibility
|
||||||
|
|
||||||
Stable releases use Semantic Versioning in the form `vMAJOR.MINOR.PATCH`, with
|
Stable releases use Semantic Versioning in the form `vMAJOR.MINOR.PATCH`, with
|
||||||
@@ -70,7 +78,8 @@ requires a deliberate update to the policy and procedure where needed.
|
|||||||
For releases at or after `v1.0.0`:
|
For releases at or after `v1.0.0`:
|
||||||
|
|
||||||
- a major release may make intentional incompatible changes;
|
- a major release may make intentional incompatible changes;
|
||||||
- a minor release may add backward-compatible behavior; and
|
- a minor release may add backward-compatible behavior or raise the Go consumer
|
||||||
|
minimum as described below; and
|
||||||
- a patch release may make backward-compatible fixes.
|
- a patch release may make backward-compatible fixes.
|
||||||
|
|
||||||
Before `v1.0.0`, a minor release may change a documented public, operational,
|
Before `v1.0.0`, a minor release may change a documented public, operational,
|
||||||
@@ -82,6 +91,36 @@ Additional compatibility promises and deliberate exceptions belong in the
|
|||||||
adopted policy or canonical contract documentation. Release notes explain
|
adopted policy or canonical contract documentation. Release notes explain
|
||||||
changes; they do not redefine current compatibility contracts.
|
changes; they do not redefine current compatibility contracts.
|
||||||
|
|
||||||
|
## Consumer Minimum And Release Toolchain
|
||||||
|
|
||||||
|
Distinguish the minimum toolchain supported for consumers from the toolchain
|
||||||
|
maintainers use to build and validate a release. Record the consumer minimum in
|
||||||
|
the language's standard metadata and explain its support meaning in canonical
|
||||||
|
documentation. Define the selected release toolchain and its setup commands in
|
||||||
|
`docs/release.md`. Toolchain selection and upgrade cadence are repository-specific
|
||||||
|
choices.
|
||||||
|
|
||||||
|
For Go repositories, normally declare the initial stable release of the selected
|
||||||
|
series as the consumer minimum, such as `go 1.26.0`. Use a higher patch minimum
|
||||||
|
when a dependency or an identified correctness fix requires it, and document
|
||||||
|
the reason.
|
||||||
|
|
||||||
|
For release-policy purposes, successful build and release validation on the
|
||||||
|
selected Go patch release are sufficient to presume support across that Go
|
||||||
|
series, subject to the declared consumer minimum and concrete evidence of
|
||||||
|
incompatibility. Investigate specific compatibility concerns as they arise.
|
||||||
|
|
||||||
|
Use the selected release toolchain for candidate validation and release builds,
|
||||||
|
including CI jobs performing those tasks. A newer validation patch version alone
|
||||||
|
leaves the consumer minimum unchanged.
|
||||||
|
|
||||||
|
A minor release may raise the Go consumer minimum, including from one Go series
|
||||||
|
to the next, both before and after `v1.0.0`. This is an explicit exception to the
|
||||||
|
backward-compatibility requirement for minor releases. Patch releases preserve
|
||||||
|
the consumer minimum. Explain an increase's impact and required consumer action
|
||||||
|
in the release note. Other incompatible changes remain subject to the versioning
|
||||||
|
rules above.
|
||||||
|
|
||||||
## Release Documentation And Preparation
|
## Release Documentation And Preparation
|
||||||
|
|
||||||
Every new release must include `docs/releases/<version>.md` in the tagged
|
Every new release must include `docs/releases/<version>.md` in the tagged
|
||||||
@@ -140,9 +179,10 @@ caches are not required. If the candidate changes, validate the new candidate.
|
|||||||
Cheap publication-state checks may be repeated immediately before pushing
|
Cheap publication-state checks may be repeated immediately before pushing
|
||||||
because local or remote state may have changed during validation.
|
because local or remote state may have changed during validation.
|
||||||
|
|
||||||
Automate mechanical guards. Do not require manual approvals, sign-offs, or
|
Automate mechanical guards. Human review gates are strongly disfavored: add one
|
||||||
routine confirmations as release gates. Version selection and
|
only through an explicit project-specific policy decision explaining the
|
||||||
a concise release summary remain preparation inputs, not separate approval rounds.
|
concrete risk and why ordinary change review or automation is insufficient.
|
||||||
|
Do not add routine confirmations for checks the command can perform itself.
|
||||||
|
|
||||||
## Publication Guards
|
## Publication Guards
|
||||||
|
|
||||||
@@ -196,7 +236,8 @@ release without replacing its contents or reusing its version.
|
|||||||
Maintain `docs/release.md` as a short, executable maintainer runbook. It must
|
Maintain `docs/release.md` as a short, executable maintainer runbook. It must
|
||||||
identify:
|
identify:
|
||||||
|
|
||||||
1. prerequisites, primary branch, and authoritative remote;
|
1. prerequisites, selected release toolchain, primary branch, and authoritative
|
||||||
|
remote;
|
||||||
2. version and release-note preparation;
|
2. version and release-note preparation;
|
||||||
3. one publication command with its selected local checks and automated guards;
|
3. one publication command with its selected local checks and automated guards;
|
||||||
4. declared deliverables, required build targets, and automation responsibilities;
|
4. declared deliverables, required build targets, and automation responsibilities;
|
||||||
|
|||||||
221
docs/release.md
221
docs/release.md
@@ -1,144 +1,157 @@
|
|||||||
# Releasing Audita
|
# Releasing Audita
|
||||||
|
|
||||||
The [release policy](policy/release.md) defines versioning, supported platforms,
|
The [release policy](policy/release.md) owns release requirements. This runbook
|
||||||
immutability, and success. This runbook implements it with
|
uses the native commands in [scripts/release.sh](../scripts/release.sh), which
|
||||||
[scripts/release.sh](../scripts/release.sh). Publication ends after a successful
|
invokes the module-selected ReleaseKit tool. A confirmed source tag completes
|
||||||
source/tag push; binary delivery runs asynchronously.
|
the release; Woodpecker reports binary delivery separately.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Use Bash, Git, and Go 1.25.5 or newer (CI uses Go 1.25.5), with dependencies
|
Use Bash and Git. The authoritative remote is `origin`,
|
||||||
available locally or downloadable. Tests themselves are offline. Work from a
|
which must point to `https://gitea.maximumdirect.net/eric/audita.git`; the
|
||||||
checkout of `main` with permission to push that branch and tags to `origin`.
|
primary branch is `main`. Source publication needs no delivery credential.
|
||||||
For this repository, `origin` is `https://gitea.maximumdirect.net/eric/audita.git`.
|
Woodpecker uses the existing `GITEA_RELEASE_TOKEN` secret for delivery.
|
||||||
The script uses the checkout's configured `origin`; set it correctly before use.
|
|
||||||
The remote must support atomic pushes. No CI credentials or Mac are needed locally.
|
|
||||||
|
|
||||||
CI uses the existing `GITEA_RELEASE_TOKEN` secret for asynchronous asset delivery.
|
The source-build minimum is Go 1.26.0, declared in [go.mod](../go.mod).
|
||||||
macOS arm64 is a best-effort development environment; no macOS binary is published.
|
This minimum is required by the selected ReleaseKit dependency.
|
||||||
|
Distributed executables do not require a Go installation. The selected release
|
||||||
|
toolchain is Go 1.26.8, matching the current Woodpecker image and distributed
|
||||||
|
runtime notices. Use that local installation for tool bootstrap, candidate
|
||||||
|
validation, and release builds. Replace the installation path below with its
|
||||||
|
actual directory:
|
||||||
|
|
||||||
## Prepare the Candidate
|
```sh
|
||||||
|
export PATH="/path/to/go1.26.8/bin:$PATH"
|
||||||
|
export GOTOOLCHAIN=local
|
||||||
|
go version
|
||||||
|
```
|
||||||
|
|
||||||
Choose an unused version following the policy. Write
|
Record the reported toolchain with the validation results. The
|
||||||
`docs/releases/<version>.md`, beginning with `# Audita <version>`, followed by a
|
[release policy](policy/release.md#consumer-minimum-and-release-toolchain)
|
||||||
short summary and any compatibility or upgrade instructions relevant to the
|
governs the distinction between the consumer minimum and release toolchain,
|
||||||
release. There are no mandatory boilerplate sections. Update affected canonical
|
support across a Go series, and versioning of minimum increases. When selecting
|
||||||
documentation with behavior changes.
|
a different release toolchain, update the CI image and applicable runtime
|
||||||
|
attribution together; a newer build patch alone need not raise the minimum.
|
||||||
|
|
||||||
End each new note with these links, substituting the selected release version for
|
Prepare dependencies before offline candidate validation:
|
||||||
`<version>`:
|
|
||||||
|
```sh
|
||||||
|
GOWORK=off GOTOOLCHAIN=local go mod download
|
||||||
|
```
|
||||||
|
|
||||||
|
If dependency preparation changes `go.mod` or `go.sum`, include those changes in
|
||||||
|
the committed candidate. ReleaseKit's CLI is registered as a Go tool dependency;
|
||||||
|
`go mod tidy` retains its dependencies and checksums. The release wrapper changes
|
||||||
|
to the repository root and invokes `go tool releasekit` with workspace discovery
|
||||||
|
and automatic toolchain downloads disabled. Go builds or reuses the selected
|
||||||
|
tool from its normal build cache. Tool preparation precedes offline candidate
|
||||||
|
validation.
|
||||||
|
|
||||||
|
## Prepare and publish the candidate
|
||||||
|
|
||||||
|
Choose an unused version under the [release policy](policy/release.md), write
|
||||||
|
`docs/releases/<version>.md`, and begin it with `# Audita <version>`. Include a
|
||||||
|
short summary, relevant compatibility or upgrade instructions, and the required
|
||||||
|
`COPYRIGHT | LICENSE | ATTRIBUTION` footer with links for the selected version.
|
||||||
|
Update affected canonical documentation with behavior changes.
|
||||||
|
|
||||||
|
End each note with this footer, substituting its complete tag for `<version>`:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/docs/oss/attribution.md)
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/docs/oss/attribution.md)
|
||||||
```
|
```
|
||||||
|
|
||||||
Check that all three paths exist in the candidate source. Retire completed
|
Commit the complete candidate on `main`, including the release note, then push
|
||||||
roadmaps when useful during preparation, preserving planned and deferred work
|
the branch first:
|
||||||
and repairing affected links. This is ordinary preparation, not a script gate.
|
|
||||||
|
|
||||||
Commit the intended changes, including the note, on `main`. The release script
|
|
||||||
requires a clean index and worktree and does not create commits. There is no
|
|
||||||
separate approval round. Published notes describe their tagged release; existing
|
|
||||||
release history does not need to be reconstructed.
|
|
||||||
|
|
||||||
## Validate and Publish
|
|
||||||
|
|
||||||
From the repository root, substitute the chosen version:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bash scripts/release.sh --publish v0.2.0
|
git push origin main
|
||||||
```
|
```
|
||||||
|
|
||||||
This single invocation:
|
Record the candidate commit. Before publication, remote `main` must identify
|
||||||
|
that exact commit. ReleaseKit checks this along with the clean candidate, note,
|
||||||
|
version, and remote tag state.
|
||||||
|
|
||||||
1. Checks the version, branch, clean candidate, matching note title, and tag absence.
|
Publish from the repository root, substituting the selected version:
|
||||||
2. Runs the default offline test suite once, then builds both required Linux targets
|
|
||||||
in a temporary directory using the same build script as CI.
|
|
||||||
3. Checks that the candidate and worktree are unchanged and that it includes the
|
|
||||||
current `origin/main` history.
|
|
||||||
4. Creates the version tag at that commit and atomically pushes the exact source
|
|
||||||
commit to `main` and the exact tag to `origin`.
|
|
||||||
5. Reports release success and exits without polling CI or checking asset availability.
|
|
||||||
|
|
||||||
To validate a prepared candidate without remote access, creating a tag, or pushing:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bash scripts/release.sh --check v0.2.0
|
bash scripts/release.sh publish VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
This is an optional diagnostic command, not a required step before `--publish`.
|
The command runs `go test -mod=readonly -count=1 ./...` once with module and
|
||||||
Publication validates within its own invocation; it does not use persistent cached
|
toolchain network access disabled, builds the profile's Linux amd64 and arm64
|
||||||
results. Both modes require the candidate note and a clean `main` checkout.
|
targets, and checks the native version output. It creates an annotated tag whose
|
||||||
|
message comes from the committed note, then pushes the exact tag object. A
|
||||||
|
successful publication completes the source release without waiting for CI or
|
||||||
|
checking artifact availability.
|
||||||
|
|
||||||
## Artifact Delivery
|
`bash scripts/release.sh check VERSION` is an optional local diagnostic. It
|
||||||
|
does not inspect remote availability or publish a tag, and it is not a required
|
||||||
|
step before `publish`, which performs its own candidate validation.
|
||||||
|
|
||||||
[Woodpecker](../.woodpecker/release.yml) runs on tag events. It invokes
|
The wrapper preserves ReleaseKit's exit status: `0` means confirmed publication;
|
||||||
[scripts/build-release.sh](../scripts/build-release.sh), then publishes:
|
`1` means a known validation or publication failure/conflict; `2` means usage,
|
||||||
|
profile, or version input error; and `3` means the push outcome remains unknown.
|
||||||
|
|
||||||
|
## Binary delivery
|
||||||
|
|
||||||
|
Woodpecker runs ReleaseKit's `ci-deliver` command for tags under
|
||||||
|
`refs/tags/v*`. The tagged `go.mod` selects the ReleaseKit version, and
|
||||||
|
`releasekit.json` supplies Audita's build and delivery settings:
|
||||||
|
|
||||||
- `audita-<version>-linux-amd64`
|
- `audita-<version>-linux-amd64`
|
||||||
- `audita-<version>-linux-arm64`
|
- `audita-<version>-linux-arm64`
|
||||||
- `SHA256SUMS`
|
- `SHA256SUMS`
|
||||||
|
|
||||||
Builds disable CGO and use the shared script's release flags. The version is in the
|
Before running Go, the workflow fetches and verifies the event tag, checks it
|
||||||
asset name and source tag; Audita has no CLI version command. Cross-compilation
|
out detached, and confirms the source tree is clean. It peels the event object
|
||||||
checks compilation, not native execution on each architecture.
|
ID to its commit before comparing it with the tag's commit, accepting both
|
||||||
|
annotated tag objects and commit IDs from Woodpecker. It copies the tagged
|
||||||
|
`go.mod` and `go.sum` into temporary module metadata outside the checkout. Go
|
||||||
|
confirms that this configuration selects ReleaseKit, then builds its CLI using
|
||||||
|
an alternate `-modfile`. The bootstrap executable and temporary files remain
|
||||||
|
outside the tagged source. The workflow runs with the selected Go 1.26.8 image,
|
||||||
|
`GOWORK=off`, and `GOTOOLCHAIN=local`.
|
||||||
|
|
||||||
The CI job status and logs show delivery progress or failure. Assets appear on
|
Delivery uses prepared-checkout mode to verify the existing tag, HEAD, and clean
|
||||||
the matching Gitea release page when delivery succeeds. A successful tag push
|
worktree without repeating the bootstrap's fetch and checkout.
|
||||||
must not be reported as confirmation that these files are available.
|
|
||||||
For a new hosted release, the release plugin uses the checked-in note at the
|
|
||||||
tag as its body, including the footer. On delivery retries it preserves
|
|
||||||
existing release information and skips files that already exist.
|
|
||||||
|
|
||||||
Consumers can download the binary for their Linux architecture and `SHA256SUMS`
|
The resulting executable receives:
|
||||||
from that page, check its listed SHA-256 digest with `sha256sum`, and make it
|
|
||||||
executable. Optional consumer checks are not maintainer release gates. Digests
|
|
||||||
detect file mismatches; they are not an independent signature of authenticity.
|
|
||||||
|
|
||||||
## Failure and Retry
|
|
||||||
|
|
||||||
**Local validation fails:** correct the issue, commit any changes, and rerun the
|
|
||||||
publication command. A failure before tag creation publishes nothing. Temporary
|
|
||||||
local build files are removed on exit.
|
|
||||||
|
|
||||||
**Push fails or the connection is interrupted:** the script retains the local tag
|
|
||||||
and reports failure rather than guessing whether publication succeeded. Inspect
|
|
||||||
remote state with the actual version:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git rev-parse 'v0.2.0^{commit}'
|
"$${bootstrap_dir}/releasekit" ci-deliver \
|
||||||
git ls-remote origin refs/heads/main refs/tags/v0.2.0
|
--repo "$${CI_WORKSPACE}" \
|
||||||
|
--prepared \
|
||||||
|
--commit "$${expected_commit}" \
|
||||||
|
"$${CI_COMMIT_TAG}"
|
||||||
```
|
```
|
||||||
|
|
||||||
If the remote tag identifies the intended candidate and its source is on remote
|
The workflow maps the existing `GITEA_RELEASE_TOKEN` secret to
|
||||||
`main`, the release succeeded; no CI waiting is needed. An atomic push normally
|
`RELEASEKIT_GITEA_TOKEN`. The complete checked-in release note, including its
|
||||||
updates both refs or neither. If `main` has advanced since publication, fetch it
|
licensing footer, supplies the hosted release body. Delivery runs independently
|
||||||
and check candidate ancestry rather than requiring tip equality.
|
after source publication; its status and logs report delivery progress or
|
||||||
|
failure.
|
||||||
|
|
||||||
If the tag is absent remotely and was never published or used by a release
|
ReleaseKit handles content-aware retries and retained-artifact recovery. See
|
||||||
channel, remove only that unpublished local tag (`git tag -d v0.2.0`), resolve
|
the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.4.1/docs/consumers/publish.md)
|
||||||
any rejected branch update, and rerun the command. If the remote tag has a
|
and [delivery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.4.1/docs/consumers/delivery.md)
|
||||||
different target, do not overwrite it; choose a fresh version. Never force a
|
for those mechanics. A delivery failure does not undo a confirmed source
|
||||||
branch or tag update to bypass the guards.
|
release. Retry the same immutable tag after resolving the reported state; do not
|
||||||
|
replace published files or a checksum manifest.
|
||||||
|
|
||||||
**CI delivery fails:** the release remains successful. Inspect the failed tag
|
## Failure and recovery
|
||||||
job's logs and retry that same tagged job for a transient failure. Publication is
|
|
||||||
configured to skip existing filenames and not overwrite assets. Keep the same
|
|
||||||
source and build environment when retrying; inspect any existing assets/checksum
|
|
||||||
manifest before filling gaps. Do not regenerate and replace a published manifest
|
|
||||||
or binary. If an existing manifest cannot describe the missing rebuilt assets,
|
|
||||||
use a new version instead. A pipeline or source defect also requires a corrected
|
|
||||||
commit and new version. An outage does not require moving the tag.
|
|
||||||
|
|
||||||
**A released source defect is discovered:** fix it on `main` and publish a new
|
Fix validation failures in a new committed candidate, then rerun `publish`.
|
||||||
appropriate version with a note explaining the correction. Leave the old tag and
|
After a failed push, ReleaseKit retains the local annotated tag and reports the
|
||||||
published files intact.
|
captured authoritative endpoint and publication state. Resolve the result
|
||||||
|
against that endpoint before retrying. An exact remote tag-object match, with
|
||||||
|
its source available on remote `main`, can confirm publication. If the remote
|
||||||
|
state cannot be read, the result remains unknown; do not remove or replace the
|
||||||
|
tag while uncertain. Follow the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.4.1/docs/consumers/publish.md)
|
||||||
|
for object-level recovery details.
|
||||||
|
|
||||||
## Maintaining the Release Workflow
|
A source-release success and artifact-delivery status are separate. For a
|
||||||
|
partial or uncertain delivery, inspect the job output and follow the pinned
|
||||||
The shared build script keeps local and CI build settings aligned. Tests in
|
[delivery recovery guidance](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.4.1/docs/consumers/delivery.md).
|
||||||
[scripts/release_test.go](../scripts/release_test.go) exercise publication against
|
A source or published-content correction requires a new appropriate version;
|
||||||
temporary local Git remotes, including guard failures and rejected pushes, with
|
leave the original tag and its contents intact.
|
||||||
Go execution stubbed to avoid recursive builds/tests. They run in `go test ./...`.
|
|
||||||
Changes to CI delivery should preserve explicit asset names, checksums, and
|
|
||||||
non-overwriting publication. CI delivery is separate from the local success path.
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
- [Audita v3.1.1](v3.1.1.md)
|
||||||
|
- [Audita v3.1.0](v3.1.0.md)
|
||||||
|
- [Audita v3.0.1](v3.0.1.md)
|
||||||
|
- [Audita v3.0.0](v3.0.0.md)
|
||||||
- [Audita v2.0.1](v2.0.1.md)
|
- [Audita v2.0.1](v2.0.1.md)
|
||||||
- [Audita v2.0.0](v2.0.0.md)
|
- [Audita v2.0.0](v2.0.0.md)
|
||||||
- [Audita v1.1.0](v1.1.0.md)
|
- [Audita v1.1.0](v1.1.0.md)
|
||||||
|
|||||||
59
docs/releases/v3.0.0.md
Normal file
59
docs/releases/v3.0.0.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Audita v3.0.0
|
||||||
|
|
||||||
|
Audita v3 adopts native configuration inheritance and layered PromptKit
|
||||||
|
definitions, and allows transcript and report destinations to be selected
|
||||||
|
independently. This is an intentional breaking release: existing PromptKit
|
||||||
|
profile files require migration from the v2 release format.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- ConfigKit is updated from v0.4.2 to v0.5.0. Application configuration supports
|
||||||
|
native sibling inheritance, and configuration explanations include value-free
|
||||||
|
inheritance provenance.
|
||||||
|
- PromptKit is updated from v0.11.2 to v0.12.0, with both backend catalogs at
|
||||||
|
`v3.20260930.0`. Native keyed definitions support profile inheritance and
|
||||||
|
operator prompt overrides. The selected `profiles_dir` can contain profiles,
|
||||||
|
backends, and prompts; application backend endpoints, capacity, and credentials
|
||||||
|
remain authoritative.
|
||||||
|
- Effective definitions and referenced prompt message contents are captured at
|
||||||
|
runtime construction. Consumed host configuration and message files are
|
||||||
|
protected from output collisions. Audita retains its per-call schemas, domain
|
||||||
|
validators, retry and repair budgets, and resilient processing behavior.
|
||||||
|
- An explicit `--report-dir` now selects reports for either file or stdout
|
||||||
|
transcript output. File output without that flag still places reports beside
|
||||||
|
the transcript. This supports Narratio's independent output destinations;
|
||||||
|
debug capture remains independently selectable.
|
||||||
|
|
||||||
|
## Upgrading from v2
|
||||||
|
|
||||||
|
1. Convert each old PromptKit profile to a keyed definition under
|
||||||
|
`profiles: {<profile-id>: ...}`, remove its inner `id`, and replace
|
||||||
|
`base_profile` with `extends`. Profile IDs come from mapping keys rather than
|
||||||
|
filenames. No compatibility loader or automatic conversion is provided.
|
||||||
|
2. Review explicit zero, omitted, and null generation controls. For example,
|
||||||
|
`temperature: 0` now sends an explicit zero, omission leaves the control
|
||||||
|
unspecified, and null suppresses an inherited control. A zero profile timeout
|
||||||
|
disables PromptKit's generation deadline; Audita's operation deadline still
|
||||||
|
applies.
|
||||||
|
3. Select a configuration root whose final definitions are valid. PromptKit now
|
||||||
|
validates unused definitions too, including backend references, and conflicting
|
||||||
|
values within a directory source group are errors. The maintained examples
|
||||||
|
use separate [local](../../examples/profiles/local/) and
|
||||||
|
[service](../../examples/profiles/service/) roots. Runtime source edits take
|
||||||
|
effect on the next invocation.
|
||||||
|
4. Rename any application backend literally named `extends` and update its
|
||||||
|
profile references. ConfigKit now reserves that key for inheritance.
|
||||||
|
5. Validate the migrated installation with
|
||||||
|
`audita config validate --config /path/to/config.yml`, including any selected
|
||||||
|
testing overlay. Application configuration continues to use `version: 2`.
|
||||||
|
|
||||||
|
The [configuration reference](../config.md#promptkit-profiles-and-operator-prompts)
|
||||||
|
contains the migrated format and presence rules. Start from the maintained
|
||||||
|
[local](../../examples/minimal-config.yml),
|
||||||
|
[service](../../examples/production-config.yml), or
|
||||||
|
[application inheritance](../../examples/configkit-inheritance.yml) examples.
|
||||||
|
The [CLI reference](../cli.md) describes report selection, and the
|
||||||
|
[release procedure](../release.md) describes source publication and asynchronous
|
||||||
|
Linux binary delivery.
|
||||||
|
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.0.0/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.0.0/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.0.0/docs/oss/attribution.md)
|
||||||
14
docs/releases/v3.0.1.md
Normal file
14
docs/releases/v3.0.1.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Audita v3.0.1
|
||||||
|
|
||||||
|
This patch release adds a configuration-free version command and adopts
|
||||||
|
ReleaseKit for source publication and Linux binary delivery. Transcript
|
||||||
|
processing, configuration, and LLM behavior are unchanged.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- `audita version` prints the application version without loading application
|
||||||
|
configuration or contacting an LLM provider.
|
||||||
|
- Release publication and Linux artifact delivery now use ReleaseKit v0.1.0.
|
||||||
|
The release runbook describes the maintainer workflow and delivery recovery.
|
||||||
|
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.0.1/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.0.1/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.0.1/docs/oss/attribution.md)
|
||||||
42
docs/releases/v3.1.0.md
Normal file
42
docs/releases/v3.1.0.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Audita v3.1.0
|
||||||
|
|
||||||
|
This minor release updates Audita's configuration, LLM, and release libraries
|
||||||
|
and raises the minimum Go version for source builds to 1.26.0. Existing Audita
|
||||||
|
configuration files, PromptKit profiles, and command behavior require no
|
||||||
|
migration.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- ConfigKit is updated from v0.5.0 to v0.7.1, with improvements to configuration
|
||||||
|
loading, resolution, provenance, and inspection efficiency.
|
||||||
|
- PromptKit is updated from v0.12.0 to v0.14.0, reducing avoidable allocations in
|
||||||
|
preparation, rendering, and execution. Audita retains its existing schemas,
|
||||||
|
validation, retry and repair budgets, and resilient processing behavior.
|
||||||
|
The libraries' new discovery and configuration-comparison APIs remain unused
|
||||||
|
by Audita.
|
||||||
|
- ReleaseKit is updated from v0.1.0 to v0.4.1. The local release launcher now
|
||||||
|
invokes the module-selected Go tool, and CI selects the tool from the tagged
|
||||||
|
module metadata. Prepared-checkout delivery reuses the tag checkout already
|
||||||
|
verified during CI bootstrap. Source publication still completes at a
|
||||||
|
successful tag push; Linux binary delivery remains asynchronous.
|
||||||
|
- Release builds and CI use Go 1.26.8, with corresponding runtime attribution
|
||||||
|
updated. The source-build minimum is tracked separately from that build
|
||||||
|
toolchain patch version.
|
||||||
|
|
||||||
|
## Upgrading from v3.0.1
|
||||||
|
|
||||||
|
Source builders must upgrade from the previous Go 1.25.5 minimum to **Go 1.26.0
|
||||||
|
or newer**, including any build environments still using Go 1.25. Maintainers
|
||||||
|
should follow the updated [release procedure](../release.md) for toolchain setup,
|
||||||
|
publication, and delivery recovery.
|
||||||
|
|
||||||
|
Go module consumers must use the v3 module path. For example:
|
||||||
|
`go install gitea.maximumdirect.net/eric/audita/v3/cmd/audita@v3.1.0`. This
|
||||||
|
aligns the module path with the v3 release line. Downloaded binaries and
|
||||||
|
commands run from a source checkout are unaffected.
|
||||||
|
|
||||||
|
Users of distributed Linux binaries need no Go installation. No configuration,
|
||||||
|
profile, transcript-format, or command-behavior migration is required for this
|
||||||
|
release.
|
||||||
|
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.1.0/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.1.0/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.1.0/docs/oss/attribution.md)
|
||||||
20
docs/releases/v3.1.1.md
Normal file
20
docs/releases/v3.1.1.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Audita v3.1.1
|
||||||
|
|
||||||
|
This patch fixes the Woodpecker bootstrap failure that prevented Linux binary
|
||||||
|
delivery for v3.1.0. Audita's transcript processing and configuration are unchanged.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Resolve Woodpecker's tag-event object ID to its source commit before checking
|
||||||
|
the tagged checkout and invoking ReleaseKit. Annotated tag events previously
|
||||||
|
failed a comparison between the tag object and commit, stopping the workflow
|
||||||
|
before binary builds began.
|
||||||
|
- Add offline regression coverage for annotated and lightweight tag events,
|
||||||
|
including rejection of an unrelated source commit.
|
||||||
|
|
||||||
|
No configuration, profile, transcript-format, or command-behavior migration is
|
||||||
|
required from v3.1.0. The Go source-build minimum remains 1.26.0. Linux amd64 and
|
||||||
|
arm64 binary delivery follows the existing asynchronous
|
||||||
|
[release procedure](../release.md#binary-delivery).
|
||||||
|
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.1.1/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.1.1/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/v3.1.1/docs/oss/attribution.md)
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
# ConfigKit Adoption Roadmap
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-24
|
|
||||||
|
|
||||||
**Status:** Production implementation complete. The selected configuration and
|
|
||||||
overlay model is implemented; [implementation evidence](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md)
|
|
||||||
records verification. No release has been published.
|
|
||||||
|
|
||||||
## Purpose and Direction
|
|
||||||
|
|
||||||
Adopt ConfigKit to harmonize Audita's configuration policy with the other
|
|
||||||
applications in the suite. Consistent configuration conventions are the primary
|
|
||||||
benefit; reducing application-owned configuration infrastructure is a secondary
|
|
||||||
benefit and a useful measure of integration quality.
|
|
||||||
|
|
||||||
This is an intentional breaking migration. There is no goal of backward
|
|
||||||
compatibility with existing installations, configuration files, CLI flags,
|
|
||||||
environment variables, or configuration-inspection output. Provide a migration
|
|
||||||
guide instead of legacy loaders, aliases, dual operating modes, or an ongoing
|
|
||||||
compatibility layer. Retain existing syntax only when it independently suits the
|
|
||||||
new model, not because compatibility requires it.
|
|
||||||
|
|
||||||
The initial review and prototype used ConfigKit v0.3.1, then verified v0.4.0
|
|
||||||
selected overlays. Those findings remain historical; the prototype was removed
|
|
||||||
after production integration. The application depends on a versioned module,
|
|
||||||
without a sibling checkout or local module replacement.
|
|
||||||
|
|
||||||
The shared boundary/prototype unit is complete. Its [selected contract, inventory,
|
|
||||||
and evidence](library-boundary-findings.md) define the detailed target alongside
|
|
||||||
this roadmap. Production configuration migration is complete.
|
|
||||||
|
|
||||||
## Target End State
|
|
||||||
|
|
||||||
- ConfigKit owns strict YAML loading, input presence, default application, explicit
|
|
||||||
override mechanics, recorded resolution, secret references, and safe inspection.
|
|
||||||
- Audita owns its configuration types, field meanings, defaults, schema-version
|
|
||||||
policy, file selection, CLI parsing, domain validation, and runtime initialization.
|
|
||||||
- Adopt ConfigKit's precedence and validation lifecycle rather than reproduce
|
|
||||||
Audita's current per-layer validation and environment-overlay behavior.
|
|
||||||
- General configuration environment overrides are retired. Credentials use typed
|
|
||||||
environment-name secret references; supplied references must resolve according
|
|
||||||
to ConfigKit's contract. File selection follows the application-owned
|
|
||||||
discovery rules in the selected boundary.
|
|
||||||
- Standardize duration representations, explicit empty/null/zero handling, and
|
|
||||||
inheritance on the shared conventions. Express genuine Audita domain rules
|
|
||||||
through the supported resolution and validation APIs.
|
|
||||||
- Retained domain-setting flags and proposal/validation profile flags become
|
|
||||||
explicit ConfigKit overrides according to the migration inventory. Remove old
|
|
||||||
provider/key/retry/concurrency flags and aliases. Credentials use secret references.
|
|
||||||
- Use ConfigKit's redacted effective output and provide source/explanation access
|
|
||||||
through the selected views below. Legacy JSON inspection is replaced.
|
|
||||||
Diagnostic configuration snapshots use the same new model.
|
|
||||||
- Delete superseded loading and override machinery. Do not hide the old model
|
|
||||||
behind adapters that defeat the adoption's consistency benefit.
|
|
||||||
|
|
||||||
Keep Audita's domain capabilities, such as ordered and repeated correction modules,
|
|
||||||
expressible in the new model. Their existing configuration spelling is not binding.
|
|
||||||
Transcript/glossary formats, processing algorithms, and output schemas are outside
|
|
||||||
this migration unless later planning identifies a concrete dependency or an
|
|
||||||
explicitly coordinated change.
|
|
||||||
|
|
||||||
## Selected Overlays for Testing
|
|
||||||
|
|
||||||
Enable ConfigKit's optional selected-overlay feature in the migration. A testing
|
|
||||||
configuration that selects cheaper, faster LLMs is the initial use case. ConfigKit
|
|
||||||
**overlays** modify application settings; PromptKit **execution profiles** still
|
|
||||||
own model and generation settings. Keep `profiles_dir`, the PromptKit `profiles/`
|
|
||||||
directory, and proposal/validation profile names unchanged by this terminology
|
|
||||||
change.
|
|
||||||
|
|
||||||
The planned interface is `--overlay <name>` on `process`, `config validate`, and
|
|
||||||
`config print-effective`. Treat selection as invocation state passed to
|
|
||||||
`Options.Overlay`, not as another persistent setting or environment overlay.
|
|
||||||
Follow ConfigKit v0.4.0's selection contract: omission, an empty name, or `default`
|
|
||||||
selects no overlay; a named selection loads `overlays/<name>.yml` beneath the selected
|
|
||||||
configuration root. A missing selected overlay is an error. Use the library's
|
|
||||||
single-overlay semantics without adding stacking, inheritance, or legacy-directory
|
|
||||||
fallback.
|
|
||||||
|
|
||||||
Precedence is application defaults, main configuration, selected overlay, then
|
|
||||||
explicit setting flags. Perform Audita's role inheritance and semantic validation
|
|
||||||
on the resolved configuration. ConfigKit's base parsing and schema-version checks
|
|
||||||
still precede overlay application; overlays cannot replace the schema version or
|
|
||||||
contain ConfigKit directives. General environment-variable overrides remain retired.
|
|
||||||
Imports and secret-directory bootstrap remain disabled.
|
|
||||||
|
|
||||||
Provide a maintained `overlays/testing.yml` example that selects testing execution
|
|
||||||
profiles through `llm.proposal_profile` and `llm.validation_profile`. Model names and
|
|
||||||
generation controls belong in the corresponding PromptKit profile files. Set both
|
|
||||||
role selections in this example so a base configuration's explicit validation
|
|
||||||
profile cannot accidentally retain the production model. Normal omission-based
|
|
||||||
validation-profile inheritance continues to apply to the final effective settings.
|
|
||||||
The example must make its backend/credential requirements clear and use no real
|
|
||||||
secrets. Model choice is operator-controlled; “testing” does not promise a price,
|
|
||||||
performance level, or equivalent correction quality.
|
|
||||||
|
|
||||||
Overlays may modify other ordinary application settings supported by the schema;
|
|
||||||
they are not restricted to profile selection. The resulting configuration must
|
|
||||||
still satisfy shared backend capacity, credential, and domain rules. Validation,
|
|
||||||
effective-config inspection, selected-profile inspection, and processing must all
|
|
||||||
use the same selected overlay. Preserve overlay provenance in configuration
|
|
||||||
explanations and diagnostics while retaining secret redaction.
|
|
||||||
|
|
||||||
The [recorded overlay results](library-boundary-findings.md#selected-overlay-extension-2026-09-24)
|
|
||||||
prove the representative load, inspection, and HTTP path. Production acceptance
|
|
||||||
requires the same selection to reach both actual LLM roles, with explicit flags
|
|
||||||
winning over overlays and surviving secret references resolved/redacted correctly.
|
|
||||||
The isolated prototype is evidence, not a second supported runtime.
|
|
||||||
|
|
||||||
## Inspection and Invocation Contract
|
|
||||||
|
|
||||||
`process`, `config validate`, and `config print-effective` share file/overlay
|
|
||||||
selection and retained setting overrides. Invocation I/O paths remain process-only.
|
|
||||||
Only explicitly supplied flags override settings. Validation requires an explicit
|
|
||||||
config file; processing and inspection use the selected discovery order. Help is
|
|
||||||
available before configuration loading. All three commands validate selected
|
|
||||||
profiles without requiring a provider call during setup.
|
|
||||||
|
|
||||||
`config print-effective --view settings|profiles|explain` exposes three YAML views:
|
|
||||||
|
|
||||||
- `settings` (default): ConfigKit's redacted effective configuration.
|
|
||||||
- `profiles`: an Audita-owned summary of each selected role's profile, backend,
|
|
||||||
model, temperature, token limit, top-p, timeout, service tier, and reasoning
|
|
||||||
effort where present. Arbitrary extra provider parameters and credentials are
|
|
||||||
omitted; this is a selected-controls view, not every execution input.
|
|
||||||
- `explain`: field paths, sources, and derivation records from ConfigKit, without
|
|
||||||
resolved secret values.
|
|
||||||
|
|
||||||
A ConfigKit snapshot alone does not describe profile-owned controls. Profile
|
|
||||||
inspection must enforce the registered-backend and credential boundary. Diagnostic
|
|
||||||
snapshots use these safe views and preserve overlay/explicit-override provenance.
|
|
||||||
The migration guide explains the intentional replacement of legacy inspection JSON.
|
|
||||||
|
|
||||||
## Integration and Completion Criteria
|
|
||||||
|
|
||||||
ConfigKit and PromptKit form one intentional breaking refactor. Intermediate
|
|
||||||
commits need not build or run. The target contains no temporary old-runtime adapter,
|
|
||||||
compatibility shim, processing gate, dual loader, or automatic legacy conversion.
|
|
||||||
The [combined feature roadmap](library-adoption.md) owns execution/reporting intent;
|
|
||||||
[implementation.md](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md) alone owns stage order and implementation tasks.
|
|
||||||
|
|
||||||
The final application loads all retained domain settings, assembles both selected
|
|
||||||
profiles, and processes through PromptKit. Controlled HTTP evidence must connect
|
|
||||||
configuration selection to actual requests, domain outcomes, safe diagnostics,
|
|
||||||
and shared finite backend capacity. Maintained examples and one migration guide
|
|
||||||
cover the complete file/flag/environment transition. Obsolete application-owned
|
|
||||||
loading, transport, rendering, and scheduler machinery is removed.
|
|
||||||
|
|
||||||
The combined target is Audita v2.0.0. Release compliance and publication remain
|
|
||||||
last and separate from implementing the refactor. Retention and transcript timestamp
|
|
||||||
changes are not included; the selected retry/repair contract resolves the retry
|
|
||||||
follow-up in [runtime behavior](runtime-behavior.md).
|
|
||||||
|
|
||||||
## Scope Boundaries
|
|
||||||
|
|
||||||
- Pin ConfigKit v0.4.0 in production and align Go/toolchain requirements. ConfigKit requires
|
|
||||||
Go 1.25; the combined PromptKit prototype requires Go 1.25.5. Adopting these libraries
|
|
||||||
does not automatically remove Audita's existing YAML dependency used for glossary parsing.
|
|
||||||
- Implement the selected configuration and final PromptKit runtime directly,
|
|
||||||
without temporary runtime adapters, compatibility shims, or process gates.
|
|
||||||
- Enable selected overlays as specified above. Keep imports and secret-directory
|
|
||||||
bootstrap disabled unless a separate concrete requirement justifies them.
|
|
||||||
- Validation and inspection resolve surviving credential references and fail on
|
|
||||||
missing or empty values; no credential-free bypass is planned.
|
|
||||||
- Inspection and diagnostics use the safe selected-profile view and ConfigKit
|
|
||||||
provenance described above. Changed formats are covered by the migration guide.
|
|
||||||
|
|
||||||
The eventual completion test is that Audita follows the suite's ConfigKit
|
|
||||||
conventions while retaining its intended domain capabilities, with a clear
|
|
||||||
migration guide and no ongoing obligation to support the previous configuration
|
|
||||||
model.
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
# Partial Success for Validator Decision Sets
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-25
|
|
||||||
|
|
||||||
**Status:** Implemented. This document owns the target behavior and user intent.
|
|
||||||
[historical implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/91d9be68a7bb3a102900253fdfd8438f0a43f666/docs/roadmap/implementation.md) owns concrete decisions, ordered work,
|
|
||||||
and verification.
|
|
||||||
|
|
||||||
## Purpose and User Intent
|
|
||||||
|
|
||||||
Preserve usable validator decisions when other entries in the same model response
|
|
||||||
have missing, duplicate, or unexpected correction indices. One defective entry
|
|
||||||
should not prevent unrelated candidates from completing their required validation.
|
|
||||||
This extends Audita's existing partial-success approach without another model call.
|
|
||||||
|
|
||||||
Implement this simpler improvement before reconsidering guided domain recovery.
|
|
||||||
Missing validation must never authorize applying a correction, but neither should
|
|
||||||
it invalidate a different candidate's unambiguous approval or editorial rejection.
|
|
||||||
The existing [resilience design](resilience.md) remains the foundation.
|
|
||||||
|
|
||||||
## Historical Baseline
|
|
||||||
|
|
||||||
The reviewed application baseline is `d2a6fcf`:
|
|
||||||
|
|
||||||
- PromptKit validates the complete model response against Audita's embedded JSON
|
|
||||||
Schema and performs its configured structural repairs. Only structurally valid,
|
|
||||||
decoded responses reach domain decision mapping.
|
|
||||||
- `mapLLMResponseToDecisions` in
|
|
||||||
[`llm_validators.go`](../../internal/framework/validators/llm_validators.go)
|
|
||||||
requires exactly one decision for every requested correction index. An empty
|
|
||||||
decision list, missing index, duplicate index, or unexpected index returns an
|
|
||||||
error instead of retaining usable decisions.
|
|
||||||
- `LLMBackedValidator.Validate` consequently marks the entire batch unavailable
|
|
||||||
with `validation_decisions_invalid`. Other batches continue, but usable decisions
|
|
||||||
within that batch are lost.
|
|
||||||
- The runner's cardinality checks enforce an internal contract: each candidate
|
|
||||||
receives one outcome from each validator it reaches. These checks apply to
|
|
||||||
Audita-owned results, not directly to raw model entries.
|
|
||||||
- Deterministic validators already filter individual proposals. Unpreviewable
|
|
||||||
candidates and oversized validation inputs are isolated; recoverable provider
|
|
||||||
failures remain batch-local; application skips individual conflicts.
|
|
||||||
- Ordinary reports distinguish failed operations from successful operations with
|
|
||||||
recoveries. Several failed operations in one stage/chunk share one work-unit
|
|
||||||
error record. The [output contract](../integrations/output-files.md) owns the
|
|
||||||
current counts and serialized representation.
|
|
||||||
|
|
||||||
The defect addressed here is the all-or-nothing domain mapping boundary, not
|
|
||||||
validation itself or the runner's internal consistency checks.
|
|
||||||
|
|
||||||
## Target Decision Semantics
|
|
||||||
|
|
||||||
Reconcile each structurally valid response against the indices actually requested
|
|
||||||
in that particular validator batch. Correction indices, rather than segment IDs
|
|
||||||
or positions in the response array, identify candidates. Several corrections may
|
|
||||||
legitimately target the same segment.
|
|
||||||
|
|
||||||
| Response condition | Target behavior |
|
|
||||||
| --- | --- |
|
|
||||||
| Exactly one valid decision for a requested index | Preserve its approval or rejection and associated decision evidence. |
|
|
||||||
| Requested index omitted | Produce an explicit validation-unavailable outcome for that candidate. |
|
|
||||||
| Requested index appears more than once | Treat that candidate's validation as unavailable; preserve unrelated decisions. |
|
|
||||||
| Index was not requested in this batch | Ignore the extraneous entry and record the handled defect. |
|
|
||||||
| Empty decision list for a nonempty request | Mark all requested candidates unavailable; do not fail the run. |
|
|
||||||
| Decisions appear in a different order | Match by identity and retain deterministic internal results. |
|
|
||||||
|
|
||||||
Duplicate handling does not choose the first or last answer, nor infer a verdict
|
|
||||||
by voting. Even identical duplicates follow the agreed duplicate-index handling;
|
|
||||||
accepting them specially is not needed for this feature. Duplicate extraneous
|
|
||||||
indices remain extraneous entries, not reasons to invalidate requested candidates.
|
|
||||||
|
|
||||||
An unavailable candidate remains unapplied and does not proceed as approved to
|
|
||||||
later validators. It is distinct from a valid `approved: false` decision, which
|
|
||||||
is ordinary editorial processing. Valid approvals continue through the remaining
|
|
||||||
required validators and apply-time checks; this feature grants no bypass.
|
|
||||||
|
|
||||||
The mapper returns one internal outcome for every requested candidate and no
|
|
||||||
outcome for an extraneous index. Preserve strict runner cardinality checks so
|
|
||||||
application bugs are still detected. Do not convert genuine internal invariant
|
|
||||||
failures, cancellation, or fatal setup failures into tolerated model defects.
|
|
||||||
|
|
||||||
## Outcomes and Reporting
|
|
||||||
|
|
||||||
Keep existing work-unit aggregation and operation accounting:
|
|
||||||
|
|
||||||
- If any requested candidate lacks an unambiguous usable decision, record a
|
|
||||||
validation-operation error identifying the affected requested indices. Other
|
|
||||||
candidates' usable decisions from that operation remain effective. An operation
|
|
||||||
error does not mean every candidate in its batch failed.
|
|
||||||
- If all requested candidates have usable decisions and extra entries were
|
|
||||||
ignored, record a successful operation with a recovery for that handled defect.
|
|
||||||
This remains a recovery even if all legitimate decisions are editorial rejections.
|
|
||||||
- If missing/duplicate requested decisions and ignored extras coexist, retain the
|
|
||||||
operation error. Follow the existing rule that failed operations do not also
|
|
||||||
produce recovery records; retain extra-entry details in debug evidence.
|
|
||||||
- A valid complete response with no handled defect adds neither error nor recovery.
|
|
||||||
Valid editorial rejection alone never adds either category.
|
|
||||||
- Preserve actual structural repair and service-retry counts. On successful
|
|
||||||
operations, consolidate applicable recovery reasons in one recovery record.
|
|
||||||
Do not count reconciliation as another LLM operation or structural repair.
|
|
||||||
|
|
||||||
Error attribution must distinguish affected candidates from the rest of the batch.
|
|
||||||
Debug evidence should preserve supplied decisions, ignored entries, and the reason
|
|
||||||
an expected candidate became unavailable. Do not fabricate candidate ledger entries
|
|
||||||
for unknown indices or mislabel missing decisions as deliberate editorial rejection.
|
|
||||||
Ordinary reports must retain safe metadata without raw transcript or response text.
|
|
||||||
|
|
||||||
Use a decision-specific recovery reason rather than treating ignored validator
|
|
||||||
entries as defective correction proposals. Concrete reason names and minimal data
|
|
||||||
model changes belong in the implementation plan. Keep YAML, Markdown, and CLI
|
|
||||||
counts consistent, with the existing reporting-disable and independent debug options.
|
|
||||||
|
|
||||||
## Ownership and Target Architecture
|
|
||||||
|
|
||||||
Audita's validator layer reconciles structurally valid model responses into
|
|
||||||
complete internal candidate outcomes. The runner preserves strict internal
|
|
||||||
cardinality and advances only approved candidates. Reporting consumes operation
|
|
||||||
facts without treating partial validation failure as loss of every decision.
|
|
||||||
Debug evidence distinguishes unavailable validation from editorial rejection.
|
|
||||||
|
|
||||||
The schema/transport boundary remains with PromptKit; reconciliation needs no
|
|
||||||
additional model call. Concrete helper structures, reason names, mapping changes,
|
|
||||||
and verification steps belong in the [historical implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/91d9be68a7bb3a102900253fdfd8438f0a43f666/docs/roadmap/implementation.md).
|
|
||||||
|
|
||||||
## Scope Boundaries
|
|
||||||
|
|
||||||
PromptKit's whole-response structural validation and repair remain unchanged.
|
|
||||||
A malformed entry, including an extraneous entry that violates the schema, may
|
|
||||||
still invalidate the response and exhaust structural repairs before reconciliation
|
|
||||||
can run. Ignoring schema-valid extras does not promise salvage from malformed JSON,
|
|
||||||
missing required fields, wrong types, or out-of-range values.
|
|
||||||
|
|
||||||
General per-entry structural salvage is a separate design question, not part of
|
|
||||||
this fix. Likewise, guided regeneration remains deferred for reconsideration after
|
|
||||||
this change; no new retries, feedback prompts, budgets, upstream dependency changes,
|
|
||||||
or operator controls are required here.
|
|
||||||
|
|
||||||
Preserve module scope, literal replacement behavior, fixed chunks, stage barriers,
|
|
||||||
span-deletion safeguards, backend capacity limits, and fatal cancellation behavior.
|
|
||||||
No additional restrictions on valid corrections or guarantees about model judgment
|
|
||||||
are introduced. This work does not itself authorize tagging or publishing v2.0.0.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- A schema-valid response can retain usable decisions despite missing, duplicate,
|
|
||||||
or unexpected indices elsewhere in the same batch.
|
|
||||||
- Only the affected requested candidates become unavailable; extras never create
|
|
||||||
candidates or attach decisions to a different batch's candidates.
|
|
||||||
- Valid negative decisions remain editorial outcomes, and every applied correction
|
|
||||||
completes all required validation.
|
|
||||||
- Internal validator results satisfy existing strict cardinality checks.
|
|
||||||
- Missing/duplicate requested decisions yield accurately attributed errors without
|
|
||||||
losing valid work. Extra-only defects yield a consolidated recovery; mixed
|
|
||||||
defects follow existing failed-operation reporting semantics.
|
|
||||||
- Final transcript, debug evidence, YAML reports, Markdown counts, and CLI summary
|
|
||||||
agree about applied work and failures/recoveries.
|
|
||||||
- Reconciliation makes no additional provider calls. Existing structural failures,
|
|
||||||
provider failures, and cancellation retain their documented handling.
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
|
|
||||||
No unresolved product-policy questions block implementation. Guided recovery and
|
|
||||||
structural salvage remain separate future decisions.
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
# Deferred Feature Work
|
# Deferred Feature Work
|
||||||
|
|
||||||
**Last updated:** 2026-09-25
|
**Last updated:** 2026-10-11
|
||||||
|
|
||||||
**Status:** Deferred or exploratory work; not implemented or scheduled. These
|
**Status:** Deferred or exploratory work; not implemented or scheduled. These
|
||||||
entries preserve scope outside the [resilience roadmap](resilience.md) and
|
entries preserve work beyond completed feature and library upgrades.
|
||||||
[span-deletion consistency roadmap](span-deletion.md). Each entry states its
|
Each entry states its intent; inclusion does not commit us to
|
||||||
intent; inclusion does not commit us to implementing every capability.
|
implementing every capability.
|
||||||
|
|
||||||
## Domain-Aware Response Recovery
|
## Domain-Aware Response Recovery
|
||||||
|
|
||||||
The [partial-success decision reconciliation](decision-set-reconciliation.md)
|
The implemented [partial-success decision reconciliation](../internal/validators.md#llm-validator-batching)
|
||||||
preserves usable decisions without another model call. Guided recovery remains
|
preserves usable decisions without another model call. Guided recovery remains
|
||||||
deferred pending evidence from failures that remain. General per-entry
|
deferred pending evidence from failures that remain. General per-entry
|
||||||
salvage from structurally invalid responses is also a separate design question;
|
salvage from structurally invalid responses is also a separate design question;
|
||||||
@@ -59,7 +59,7 @@ No new scheduler, global limiter, or execution mode is selected by this entry.
|
|||||||
|
|
||||||
Potential future work, subject first to evaluating whether it is worth implementing
|
Potential future work, subject first to evaluating whether it is worth implementing
|
||||||
at all. Empty replacement text and deletion of a span within a surviving segment
|
at all. Empty replacement text and deletion of a span within a surviving segment
|
||||||
are covered by the accepted [span-deletion consistency roadmap](span-deletion.md).
|
are covered by the implemented [span-deletion behavior](../internal/modules.md#proposal-generation-ownership).
|
||||||
That feature does not remove segment records or permit blank resulting segments.
|
That feature does not remove segment records or permit blank resulting segments.
|
||||||
|
|
||||||
Before designing segment removal, review representative production examples to
|
Before designing segment removal, review representative production examples to
|
||||||
@@ -78,3 +78,19 @@ semantics. It must address meaning preservation and the effects on segment
|
|||||||
identity, timestamps, fixed chunks, subsequent stages, recombination, and output
|
identity, timestamps, fixed chunks, subsequent stages, recombination, and output
|
||||||
consumers. Do not infer authorization to remove a segment from an empty string
|
consumers. Do not infer authorization to remove a segment from an empty string
|
||||||
or introduce segment-removal machinery as part of span-deletion consistency.
|
or introduce segment-removal machinery as part of span-deletion consistency.
|
||||||
|
|
||||||
|
## Library Inspection and Configuration Comparison
|
||||||
|
|
||||||
|
ConfigKit and PromptKit provide effective-configuration comparison; PromptKit
|
||||||
|
also provides prompt/profile listings and contextual prompt requirement
|
||||||
|
inspection. Exposing these capabilities in Audita is deferred until a concrete
|
||||||
|
operator or development need arises. The dependency upgrade did not select new
|
||||||
|
inspection commands.
|
||||||
|
|
||||||
|
If a need emerges, define command ownership, selection syntax, report scope, and
|
||||||
|
disclosure before implementation. Reuse the libraries' public APIs and consider
|
||||||
|
shared CLI conventions alongside any separately selected CommandKit adoption.
|
||||||
|
Requirement discovery is advisory evidence rather than proof of execution
|
||||||
|
readiness; an empty configuration diff does not prove equivalent execution.
|
||||||
|
The library-upgrade review remains available in
|
||||||
|
[Git history](https://gitea.maximumdirect.net/eric/audita/src/commit/4c5536ab78c72dea58d03b3c51834138d429a59a/docs/roadmap/imports.md).
|
||||||
|
|||||||
@@ -1,313 +0,0 @@
|
|||||||
# Implementation Plan: Standard Policy Alignment
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-28
|
|
||||||
|
|
||||||
**Status:** Complete; Stages 1–4 implemented.
|
|
||||||
**Audience:** gpt-5.6-terra coding agent.
|
|
||||||
**Audita planning baseline:** `91d9be68a7bb3a102900253fdfd8438f0a43f666`.
|
|
||||||
**Selected upstream revision:** `d84a38ebab26f048cafbe59a2246700c35d29843`.
|
|
||||||
|
|
||||||
## Scope and Execution
|
|
||||||
|
|
||||||
Implement the accepted [Standard Policy Alignment roadmap](policy.md). It owns
|
|
||||||
the intended end state and reasons for local variations; this plan owns ordered
|
|
||||||
implementation and verification. Complete Stage 1 through Stage 4 in order, each
|
|
||||||
as one focused agent task. Record completion evidence under each stage and commit
|
|
||||||
completed units with short, plain English messages under the agent policy.
|
|
||||||
|
|
||||||
Begin with [development guidance](../development.md) and the
|
|
||||||
[agent policy](../policy/agents.md). Read the policies governing each stage;
|
|
||||||
reuse unchanged policy context. For policy edits, read that policy and affected
|
|
||||||
cross-references. Follow the adopted selective reading guide once installed.
|
|
||||||
Use graph-first discovery for application code; document and shell/configuration
|
|
||||||
inspection does not need a graph lookup.
|
|
||||||
|
|
||||||
Use the public upstream repository at
|
|
||||||
`https://gitea.maximumdirect.net/eric/policy` and check out the exact revision above
|
|
||||||
in a temporary directory outside Audita. Verify its full commit before copying.
|
|
||||||
Read its `docs/adoption.md`, relevant `docs/policy/` files, `COPYRIGHT`, and
|
|
||||||
referenced assets. Do not replace the selected revision with current main or
|
|
||||||
reconstruct prior adoption baselines. The public repository and its attribution
|
|
||||||
are now explicitly approved.
|
|
||||||
|
|
||||||
This plan replaces the completed decision-set plan, preserved at
|
|
||||||
[the planning baseline](https://gitea.maximumdirect.net/eric/audita/src/commit/91d9be68a7bb3a102900253fdfd8438f0a43f666/docs/roadmap/implementation.md).
|
|
||||||
Repair historical roadmap references in Stage 4; do not reopen those features.
|
|
||||||
|
|
||||||
Implement directly toward the final state. Intermediate commits need no temporary
|
|
||||||
policy files, compatibility machinery, or complete dependency inventory. Keep
|
|
||||||
partial work truthful and finish cross-document consistency by Stage 4. Do not
|
|
||||||
release, tag, push, rewrite published material, change dependency versions, or
|
|
||||||
refactor application code as part of this work. No extra approval round is needed
|
|
||||||
between stages. If factual verification uncovers a concrete unresolved rights or
|
|
||||||
publication issue, report it rather than inventing an obligation or claiming it
|
|
||||||
resolved.
|
|
||||||
|
|
||||||
## Common Adoption Decisions
|
|
||||||
|
|
||||||
- Start each policy from the selected upstream file. Preserve shared substantive
|
|
||||||
wording verbatim. Remove its `Base Policy Template` section after fulfilling
|
|
||||||
the instructions; include only the project customizations selected below.
|
|
||||||
- Retain the upstream base version and set `Last Updated` to the actual adoption
|
|
||||||
date. Add `**Upstream Source:**` with a Markdown link to that same file at the
|
|
||||||
full selected commit, using the URL prefix
|
|
||||||
`https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/`.
|
|
||||||
Do not claim this was the source of old local documents.
|
|
||||||
- Keep discovery URLs in `meta.md` pointed to the public repository and
|
|
||||||
`/src/branch/main/docs/adoption.md`. Pin example links in the adopted OSS policy
|
|
||||||
to this selected commit, after checking their paths. Preserve examples upstream.
|
|
||||||
- Explain substantive local variations briefly where they apply. No separate
|
|
||||||
exception register, synchronization system, policy snapshot tests, or legal
|
|
||||||
approval workflow is needed.
|
|
||||||
- Keep policy credit in the single canonical attribution entry. Describe only
|
|
||||||
material adopted so far, expanding it as later stages copy more material.
|
|
||||||
Metadata and template removal mean an adopted file can truthfully be described
|
|
||||||
as adapted even when its substantive rules are verbatim.
|
|
||||||
- Ordinary link/text review is sufficient for documentation changes. Do not run
|
|
||||||
application tests simply because a policy document changed. Each stage ends
|
|
||||||
with `git diff --check` and review of its staged diff before committing.
|
|
||||||
|
|
||||||
## Stage 1 — Establish Provenance and Shared Agent/Testing Policies
|
|
||||||
|
|
||||||
**Completed:** 2026-09-28. Adopted meta 1.0, agents 1.3, and testing 1.3 from
|
|
||||||
the verified upstream commit, preserving Audita's graph-first discovery rule.
|
|
||||||
The initial attribution and scoped copyright notice identify only these three
|
|
||||||
adapted policies; the CC-BY-SA text matches upstream byte-for-byte. Substantive
|
|
||||||
policy text, pinned source paths, local links, and `git diff --check` were checked.
|
|
||||||
|
|
||||||
**Files:** `docs/policy/meta.md`, `docs/policy/agents.md`,
|
|
||||||
`docs/policy/testing.md`, `docs/oss/attribution.md`,
|
|
||||||
`docs/oss/licenses/CC-BY-SA-4.0.txt`, and `COPYRIGHT`.
|
|
||||||
|
|
||||||
1. Adopt meta 1.0, agents 1.3, and testing 1.3 with the common metadata and
|
|
||||||
template-removal rules. Preserve the existing Codebase Discovery section as
|
|
||||||
the one local addition to agents: Audita deliberately uses graph-first
|
|
||||||
discovery. Adopt the remaining agent rules and the testing rules verbatim.
|
|
||||||
2. Create attribution with `# Open Source Attribution Notices`, followed directly
|
|
||||||
by the canonical entry from `examples/licensing/policy-attribution.md`.
|
|
||||||
Substitute the selected commit in its COPYRIGHT, LICENSE, and Source URLs.
|
|
||||||
Initially identify the three newly adapted policy files in its `Material:`
|
|
||||||
sentence. Do not claim the remaining files have already been updated.
|
|
||||||
3. Copy the selected revision's `docs/oss/licenses/CC-BY-SA-4.0.txt` byte-for-byte.
|
|
||||||
Keep the supplied upstream copyright and links to its grant/disclaimer.
|
|
||||||
4. Add the adoption guide's scoped CC-BY-SA notice to Audita's `COPYRIGHT`, with
|
|
||||||
affected material identified through attribution. Preserve Audita's name,
|
|
||||||
copyright, GPL-3.0-or-later application grant, warranty statement, and third-party
|
|
||||||
statement. Leave the root GPLv3 `LICENSE` unchanged.
|
|
||||||
5. Keep metadata local and truthful. `meta.md` points to the local attribution
|
|
||||||
and developer guide even though selective reading guidance is added next.
|
|
||||||
|
|
||||||
**Validation and completion:** Compare the substantive agent/testing text directly
|
|
||||||
with the selected upstream documents, allowing only template removal, metadata,
|
|
||||||
and the identified discovery addition. Verify each new source link's exact file
|
|
||||||
and revision, local attribution links, copied license bytes, and narrow grant
|
|
||||||
scope. No application test or publication is required.
|
|
||||||
|
|
||||||
## Stage 2 — Customize Remaining Policies and Install Selective Orientation
|
|
||||||
|
|
||||||
**Completed:** 2026-09-28. Adopted architecture 1.3, documentation 1.11,
|
|
||||||
release 1.4, and OSS 1.8 with the selected Audita variations. Installed the
|
|
||||||
upstream root orientation and verbatim Policy Reading Guide, expanded the
|
|
||||||
scoped attribution, and repaired the release documentation heading link.
|
|
||||||
Checked versions, provenance and example destinations, local links, policy
|
|
||||||
differences, and `git diff --check`.
|
|
||||||
|
|
||||||
**Files:** `docs/policy/architecture.md`, `docs/policy/documentation.md`,
|
|
||||||
`docs/policy/release.md`, `docs/policy/oss.md`, `AGENTS.md`,
|
|
||||||
`docs/development.md`, `docs/oss/attribution.md`, and `COPYRIGHT` as needed for
|
|
||||||
accurate copied-guidance scope.
|
|
||||||
|
|
||||||
1. Adopt architecture 1.3. Retain the upstream general Purpose And Ownership,
|
|
||||||
Architectural Approach, and Adopted Constraints And Implemented Structure
|
|
||||||
guidance. Replace Application-Specific Topics with Audita's established
|
|
||||||
purpose, ownership/dependency boundaries, correction/concurrency rules,
|
|
||||||
recovery, state/compatibility, secret handling, and selected library ownership.
|
|
||||||
Reuse existing local decisions; avoid duplicating general upstream prose.
|
|
||||||
Replace example threat discussion with the existing secret/input boundaries
|
|
||||||
and canonical links where useful. Do not infer new threat exclusions,
|
|
||||||
filesystem guarantees, interfaces, or package restructuring from the template.
|
|
||||||
2. Adopt documentation 1.11. Keep applicable upstream ownership rows verbatim;
|
|
||||||
omit public HTTP and public Go API rows. Add the existing concrete ownership
|
|
||||||
of `docs/integrations/output-files.md` and contributor change recipes, and
|
|
||||||
retain the deliberate retirement of standalone troubleshooting documentation.
|
|
||||||
Account for the retained agent discovery/commit responsibilities in its owner
|
|
||||||
row. Keep reading triggers in development guidance, not a second policy table.
|
|
||||||
3. Adopt release 1.4. Retain upstream completion, validation, guard, immutability,
|
|
||||||
recovery, and preparation language. Preserve the existing Supported Platforms
|
|
||||||
table and unsupported-platform statement as Audita's support commitment.
|
|
||||||
Replace the upstream discretionary human-gate paragraph with Audita's selected
|
|
||||||
rule against manual approvals, sign-offs, and routine confirmations as release
|
|
||||||
gates. Remove the obsolete list describing now-shared upstream rules as local
|
|
||||||
deviations. Keep concrete atomic-push mechanics in the runbook.
|
|
||||||
4. Adopt OSS 1.8. Add a concise Audita context paragraph identifying the existing
|
|
||||||
GPL-3.0-or-later grant and linking to COPYRIGHT and the release procedure for
|
|
||||||
Gitea distribution. Use the upstream House Format and interpretation language
|
|
||||||
verbatim. Pin application/library COPYRIGHT and attribution examples to the
|
|
||||||
selected upstream commit; remove template-only setup text. No license change
|
|
||||||
to application code or blanket dependency compatibility claim is authorized.
|
|
||||||
5. Replace root orientation with upstream `agents/AGENTS.md` wording while
|
|
||||||
retaining any existing applicable repository instructions. Keep detailed
|
|
||||||
discovery rules in their current agent-policy owner rather than duplicating
|
|
||||||
them in a newly created block.
|
|
||||||
6. Insert the adoption guide's Policy Reading Guide and accompanying reading rules
|
|
||||||
into `docs/development.md` verbatim. Remove the blanket architecture-reading
|
|
||||||
instruction; preserve contributor commands and component-specific task rows.
|
|
||||||
Adjust cross-links that would otherwise contradict selective reading.
|
|
||||||
7. Expand the single policy attribution entry to identify all `docs/policy/`
|
|
||||||
documents, adapted root orientation, and the copied Policy Reading Guide in
|
|
||||||
`docs/development.md`. Scope the COPYRIGHT CC-BY-SA notice to those materials,
|
|
||||||
not unrelated contributor prose or application documentation. Keep the material
|
|
||||||
sentence factual and concise; retain the pinned upstream credit links.
|
|
||||||
8. Find and repair local links to policy headings renamed by adoption, including
|
|
||||||
the release correction heading. Do not edit published historical release
|
|
||||||
notes to retrofit new headings or licensing conventions.
|
|
||||||
|
|
||||||
**Validation and completion:** Compare all seven policies to the pinned source;
|
|
||||||
review every substantive difference against the roadmap's justified variations.
|
|
||||||
Check versions (1.3, 1.3, 1.11, 1.3, 1.4, 1.0, 1.8 for agents, architecture,
|
|
||||||
documentation, testing, release, meta, OSS respectively), file-specific source
|
|
||||||
links, removed template sections, example destinations, ownership consistency,
|
|
||||||
and selective reading. Temporary pending release/attribution implementation is
|
|
||||||
completed by the following stages; do not create scaffolding for it.
|
|
||||||
|
|
||||||
## Stage 3 — Complete Component Attribution and README Navigation
|
|
||||||
|
|
||||||
**Completed:** 2026-09-28. Both CGO-disabled Linux target dependency lists
|
|
||||||
selected the same eight production modules. Inspected their selected source
|
|
||||||
notices and copied assets, excluded test-only dependencies, and resolved the
|
|
||||||
Go 1.25.5 source tag and its four incorporated vendored modules. Added 14
|
|
||||||
House Format entries including policy provenance, complete applicable license
|
|
||||||
texts, and the README footer. Checked 33 pinned source and notice destinations,
|
|
||||||
license bytes, local links, entry shape, and `git diff --check`.
|
|
||||||
|
|
||||||
**Files:** `docs/oss/attribution.md`, applicable texts under
|
|
||||||
`docs/oss/licenses/`, and `README.md`. Add factual exception treatment to the OSS
|
|
||||||
policy only if the actual inventory uncovers a concrete need.
|
|
||||||
|
|
||||||
1. Inventory actual incorporated material using the selected modules, production
|
|
||||||
package dependencies for both Linux targets, source/assets, and shipped Go
|
|
||||||
runtime. Use `go list -deps` for `./cmd/audita` with `CGO_ENABLED=0` and each
|
|
||||||
target architecture, plus source inspection for copied material. Inspect test
|
|
||||||
dependencies where relevant to included material; a go.mod entry or tool use
|
|
||||||
alone is not sufficient reason for attribution. Do not change go.mod/go.sum.
|
|
||||||
2. At the planning baseline, production dependencies include ConfigKit v0.4.1,
|
|
||||||
PromptKit v0.11.1, both catalogs v2.20260926.0, jsonschema/v6 v6.0.2,
|
|
||||||
yaml/v4 v4.0.0-rc.6, x/text v0.14.0, and yaml.v3 v3.0.1. Start with the four
|
|
||||||
verified source commits recorded in the feature roadmap; resolve remaining
|
|
||||||
module versions to exact source commits and inspect their COPYRIGHT, LICENSE,
|
|
||||||
NOTICE, PATENTS, and relevant bundled material as applicable. Account for
|
|
||||||
additional material discovered in that inspection rather than treating this
|
|
||||||
starting list as an exhaustive guarantee.
|
|
||||||
3. For the Go runtime, use the release build's configured Go 1.25.5 source tag
|
|
||||||
and resolve it to its full source commit. Do not silently substitute the
|
|
||||||
inspecting agent's installed toolchain (Go 1.26.8 during planning). Preserve
|
|
||||||
actual runtime/standard-library notices, including relevant bundled third-party
|
|
||||||
material. Document maintenance of this entry when the distributed toolchain
|
|
||||||
changes; do not pin new toolchain behavior or reject other supported local Go
|
|
||||||
versions merely to simplify attribution.
|
|
||||||
4. Add entries following OSS 1.8 exactly, with selected module/version or material
|
|
||||||
scope where useful. Preserve supplied copyrights and accurate license
|
|
||||||
expressions; use the prescribed link labels and `[Source](COMMIT_URL).`
|
|
||||||
spelling. Keep policy attribution first. Order other entries consistently by
|
|
||||||
project name; ordering is presentation, not a new public contract.
|
|
||||||
5. Copy applicable complete standard texts from verified sources, using the
|
|
||||||
pinned reference collection where applicable. Share identical standard texts
|
|
||||||
and include required companions (LGPLv3 and GPLv3 for the two libraries).
|
|
||||||
Include upstream commit links when a file contains project-specific material
|
|
||||||
in addition to standard wording. Preserve existing incorporated notices.
|
|
||||||
6. Apply the adopted house interpretation for project-specific notice links.
|
|
||||||
Put concrete exception reasoning in OSS policy or an appropriate decision
|
|
||||||
record, never in attribution. Do not add missing-copyright filler, research
|
|
||||||
narratives, inferred licensing choices, or legal conclusions to entries.
|
|
||||||
7. End README with the two OSS statements, identifying Audita as GPL v3.0 or later
|
|
||||||
and linking to `COPYRIGHT` and `docs/oss/attribution.md`. The linked grant owns
|
|
||||||
the separately licensed policy scope; no duplicated notice list is needed.
|
|
||||||
|
|
||||||
**Validation and completion:** Inspect entries against selected source evidence,
|
|
||||||
resolve every source/notice commit and path, compare standard license bytes, and
|
|
||||||
check local links. Confirm that actual distributed material is accounted for
|
|
||||||
without blindly including dependency graph or development-tool entries. Record
|
|
||||||
concise inventory/check evidence under this stage; no new SBOM, scanner, durable
|
|
||||||
inventory generator, or test suite is required.
|
|
||||||
|
|
||||||
## Stage 4 — Integrate Release Notes and Finish Cross-Document Validation
|
|
||||||
|
|
||||||
**Completed:** 2026-09-28. Wired the checked-in release note into the hosted
|
|
||||||
release plugin, documented immutable-tag notice links and release preparation,
|
|
||||||
repaired the specified historical roadmap references, and marked the feature
|
|
||||||
roadmap implemented. Parsed the YAML, substituted `v2.0.0` to a tracked note,
|
|
||||||
checked non-overwrite settings and candidate footer paths, reviewed all seven
|
|
||||||
policies against the pinned source, and checked repository-local Markdown links
|
|
||||||
and `git diff --check`. Hosted execution awaits the next real release.
|
|
||||||
|
|
||||||
**Files:** `.woodpecker/release.yml`, `docs/release.md`, affected documentation
|
|
||||||
links, `docs/roadmap/policy.md`, and this plan. Repair the historical references
|
|
||||||
specified below; do not retire unrelated roadmaps in this sprint.
|
|
||||||
|
|
||||||
1. Add this setting to the existing `publish-release` step:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
note: docs/releases/${CI_COMMIT_TAG}.md
|
|
||||||
```
|
|
||||||
|
|
||||||
The [official release-plugin documentation](https://woodpecker-ci.org/plugins/release)
|
|
||||||
supports a file path in `note`; the
|
|
||||||
[environment reference](https://woodpecker-ci.org/docs/usage/environment)
|
|
||||||
makes `CI_COMMIT_TAG` available for configuration substitution. These interfaces
|
|
||||||
were checked during planning. Use the checked-out tagged note directly; no
|
|
||||||
generator, custom publisher, or additional dependency is needed. Preserve
|
|
||||||
`overwrite: false`, `file-exists: skip`, existing assets/checksum settings,
|
|
||||||
secret reference, image, and step dependency.
|
|
||||||
2. In `docs/release.md`, add the footer preparation instruction with this pattern
|
|
||||||
(substitute the selected release version for `<version>`):
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/docs/oss/attribution.md)
|
|
||||||
```
|
|
||||||
|
|
||||||
Explain that the plugin uses the checked-in note for new hosted releases and
|
|
||||||
preserves existing release information on retries. Keep old notes and pages
|
|
||||||
intact. Validate intended paths locally during preparation; do not add remote
|
|
||||||
footer checks, a footer parser, or a new release-script gate.
|
|
||||||
3. Align the runbook with the adopted policy: retain `origin`, `main`, atomic
|
|
||||||
source/tag publication, one offline validation pass, required Linux builds,
|
|
||||||
optional check mode, and asynchronous immutable delivery retries. Reference
|
|
||||||
the policy for requirements rather than introducing additional ones. Add
|
|
||||||
proportionate completed-roadmap retirement guidance for future release
|
|
||||||
preparation without making it a script-enforced condition. No release script
|
|
||||||
change is expected from this adoption.
|
|
||||||
4. Repair both current-plan links in `decision-set-reconciliation.md` to the
|
|
||||||
historical plan at the full Audita planning baseline listed above. Remove
|
|
||||||
the stale sentence in `library-adoption.md` saying the current implementation
|
|
||||||
plan covers span deletion. Preserve historical records and deferred work.
|
|
||||||
5. Review the final adoption diff against upstream and the feature roadmap.
|
|
||||||
Check all policy source metadata, template removal, local exception reasons,
|
|
||||||
copied-material scope, license text fidelity, local links/anchors, and pinned
|
|
||||||
upstream destinations. Ensure the feature roadmap records implemented status
|
|
||||||
only after its accepted outcomes are achieved.
|
|
||||||
6. Parse/review the affected YAML using an available YAML parser or Woodpecker
|
|
||||||
linter. Substitute a representative existing release tag locally and confirm
|
|
||||||
the note path resolves to the tracked note; old notes need not have the new
|
|
||||||
footer. Check a temporary example of the new footer against candidate paths.
|
|
||||||
Verify non-overwriting publication settings remain unchanged. Do not call the
|
|
||||||
forge to create a test release, run a plugin with production credentials, or
|
|
||||||
wait for CI. Static configuration and documented plugin-interface validation
|
|
||||||
are sufficient for this wiring change; report that hosted execution awaits
|
|
||||||
the next real release rather than claiming it was exercised.
|
|
||||||
|
|
||||||
**Validation and completion:** Run `git diff --check`, review staged content and
|
|
||||||
all affected links, and record the final verification evidence. No full Go suite
|
|
||||||
or build matrix is required when only documents and the note setting changed.
|
|
||||||
If an actual release-script behavior change becomes necessary, justify its
|
|
||||||
scope and run focused `go test ./scripts` coverage for that behavior rather than
|
|
||||||
inventing additional release gates. Commit the completed work; leave a clean
|
|
||||||
workspace without publishing.
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
|
|
||||||
None requiring a policy or design decision before implementation. The upstream
|
|
||||||
revision, local exceptions, attribution arrangement, target documents, and
|
|
||||||
release-body mechanism are selected. Exact component notice details and source
|
|
||||||
revisions are factual verification work in Stage 3, not permission gates. If
|
|
||||||
that inspection reveals an uncovered obligation, follow the adopted exception
|
|
||||||
procedure and identify the concrete decision instead of silently expanding scope.
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Coordinated ConfigKit and PromptKit Adoption
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-28
|
|
||||||
|
|
||||||
**Status:** Production implementation complete. ConfigKit v0.4.2 and PromptKit
|
|
||||||
v0.11.2 are adopted; [historical implementation evidence](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md) records the
|
|
||||||
completed checks. Release compliance and publication remain separate work.
|
|
||||||
|
|
||||||
## Goal and Ownership
|
|
||||||
|
|
||||||
Adopt ConfigKit and PromptKit to align Audita with the application suite's shared
|
|
||||||
configuration and LLM execution conventions. Consistency is the primary benefit;
|
|
||||||
removing overlapping application infrastructure is a secondary benefit. Preserve
|
|
||||||
Audita's transcript-correction capabilities and domain protections.
|
|
||||||
|
|
||||||
| Owner | Responsibilities |
|
|
||||||
| --- | --- |
|
|
||||||
| ConfigKit | Strict application configuration loading, presence/default semantics, explicit overrides, recorded resolution, secret references, and safe inspection |
|
|
||||||
| PromptKit | Prompt/profile formats and rendering, provider calls, structural output validation and repair, transient retries, engine-local backend admission, and execution observations |
|
|
||||||
| Audita | Setting meanings, file/CLI selection, role/profile association, normalization/chunking, ordered modules, domain validation, correction application, reports, diagnostic redaction, and artifact lifecycle |
|
|
||||||
|
|
||||||
The [configuration roadmap](configkit.md) owns configuration and inspection policy.
|
|
||||||
The [boundary findings](library-boundary-findings.md) own the selected defaults,
|
|
||||||
full migration inventory, failure mapping, and prototype evidence. The selected
|
|
||||||
production versions are ConfigKit v0.4.2 and PromptKit v0.11.2, with Go 1.25.5 as
|
|
||||||
the minimum. Use published modules, never sibling checkouts or local replacements.
|
|
||||||
The existing YAML dependency remains while glossary parsing needs it.
|
|
||||||
|
|
||||||
## Migration Posture
|
|
||||||
|
|
||||||
This is an intentional breaking transition targeting Audita v2.0.0. No backward
|
|
||||||
compatibility is required for existing installations, config files, CLI flags,
|
|
||||||
environment overlays, or inspection output. Provide one migration guide, without
|
|
||||||
legacy loaders, aliases, dual modes, or automatic conversion tooling.
|
|
||||||
|
|
||||||
Both libraries belong to one refactor. Intermediate commits need not build or run;
|
|
||||||
there is no supported intermediate product state. No temporary adapters, compatibility
|
|
||||||
shims, or processing gates are needed. The complete application must work and pass
|
|
||||||
its checks before the refactor is finished. Release compliance and publication
|
|
||||||
come last, with no intermediate public release.
|
|
||||||
|
|
||||||
## Configuration and Execution Target
|
|
||||||
|
|
||||||
ConfigKit selected overlays enable a testing configuration that chooses cheaper or
|
|
||||||
faster models. The example selects both proposal and validation profiles. ConfigKit
|
|
||||||
application overlays and PromptKit execution profiles remain separate concepts.
|
|
||||||
Complete operator-maintained profiles own model/generation controls; embedded
|
|
||||||
application prompts and schemas are not operator-selected files. No catalog wiring,
|
|
||||||
profile-inheritance feature, ConfigKit imports, or secret-directory bootstrap is
|
|
||||||
part of this migration.
|
|
||||||
|
|
||||||
Capacity controls serve two needs: respecting a local service's hard simultaneous-
|
|
||||||
query limit and bounding concurrent paid API activity. One finite limit per backend
|
|
||||||
is shared across both roles and every initial, retry, and repair attempt. Limits
|
|
||||||
are engine-local, not coordinated across processes. No additional global or
|
|
||||||
per-role limiter or fine-grained scheduling policy is required. Profiles must use
|
|
||||||
registered backends and ConfigKit-resolved credentials; endpoint-only bypass and
|
|
||||||
competing profile credential lookup are excluded.
|
|
||||||
|
|
||||||
Structural repair is enabled by default for both roles because additional attempts
|
|
||||||
to obtain valid output are desired. Transient retries and structural repairs have
|
|
||||||
separate budgets; explicit zero disables the respective behavior. Each generation,
|
|
||||||
including a repair, receives its own transient-retry budget. The Audita logical-call
|
|
||||||
deadline covers preparation, waiting, backoff, all generations, and repairs; profile
|
|
||||||
HTTP timeouts apply per attempt. Exact defaults and bounds are in the
|
|
||||||
[selected contract](library-boundary-findings.md#configuration-and-profile-contract).
|
|
||||||
No Audita outer retry loop restores the old broader retry policy.
|
|
||||||
|
|
||||||
Audita retains all four correction modules, five LLM-validator prompts,
|
|
||||||
deterministic validators, confidence/applicability checks, glossary protection,
|
|
||||||
validation batching/cardinality, deterministic proposal and ledger ordering,
|
|
||||||
transcript encoders, and artifact retention. PromptKit-valid JSON still passes
|
|
||||||
Audita's domain checks. Repair exhaustion, provider failure, and domain rejection
|
|
||||||
follow the [selected failure table](library-boundary-findings.md#execution-and-failure-mapping).
|
|
||||||
Retention and transcript timestamp changes remain separate work; this migration
|
|
||||||
resolves the [retry-count follow-up](runtime-behavior.md).
|
|
||||||
|
|
||||||
## Reporting and Diagnostic Target
|
|
||||||
|
|
||||||
Keep stable Audita-owned serialized records; do not expose raw library structs.
|
|
||||||
Use process report schema `v2` with the existing domain outcomes and a new
|
|
||||||
`llm_summary` separating logical calls, dispatched provider attempts, transient
|
|
||||||
retries, structural repairs, and usage. Transcript schema versions remain unchanged.
|
|
||||||
The implementation plan specifies the concrete new fields and artifact wiring.
|
|
||||||
|
|
||||||
Usage distinguishes known zero, unknown, and incomplete totals. Preserve observations
|
|
||||||
from failed calls even when no final result exists. Count terminal totals once;
|
|
||||||
attempt observations provide diagnosis without being added to those totals again.
|
|
||||||
This is operational evidence, not a complete billing ledger.
|
|
||||||
|
|
||||||
Replace global/role scheduler utilization with per-backend configured capacity and
|
|
||||||
observed queue/generation/retry/logical-call timing. Do not fabricate measurements
|
|
||||||
that the event API does not provide. Keep domain timing and deterministic correction
|
|
||||||
ledger ordering. Record prompt definition/rendered hashes and selected profile/model
|
|
||||||
without promising the old hash values. Callbacks are short, concurrency-safe, and
|
|
||||||
free of blocking I/O or waits on their own execution.
|
|
||||||
|
|
||||||
Effective configuration and selected-profile snapshots remain distinct safe views.
|
|
||||||
Secret values must not escape through inspection, logs, reports, runtime errors,
|
|
||||||
or retained diagnostic files. Observation records exclude payloads, URLs, raw
|
|
||||||
provider messages, prompts, and credentials. Existing artifact lifecycle and
|
|
||||||
partial-failure handling remain Audita responsibilities.
|
|
||||||
|
|
||||||
## Completion Criteria and Release Boundary
|
|
||||||
|
|
||||||
- Both libraries own their selected responsibilities in the production application.
|
|
||||||
Superseded loaders, transport, renderers, schedulers, aliases, and the isolated
|
|
||||||
prototype are gone once its useful evidence is represented in production tests.
|
|
||||||
- Offline tests prove configuration-to-execution routing for both roles, selected
|
|
||||||
overlays and explicit overrides, shared backend capacity, retry/repair outcomes,
|
|
||||||
cancellation/deadlines, domain decisions, redaction, and truthful partial reports.
|
|
||||||
Tests exercise real internal collaborators and controlled HTTP boundaries.
|
|
||||||
- Maintained examples and canonical docs describe the implemented final model.
|
|
||||||
One migration guide covers files, flags, environment inputs, profiles, credentials,
|
|
||||||
overlays, inspection, and changed runtime/report/diagnostic semantics.
|
|
||||||
- The assembled application passes its required checks; Linux amd64 and arm64 build.
|
|
||||||
macOS arm64 remains best effort. No live-provider or CI-success gate is added.
|
|
||||||
|
|
||||||
PromptKit v0.11.0's reviewed retry, observation, and capacity APIs remove the earlier
|
|
||||||
upstream feature prerequisites; no upstream edit is required for adoption. The
|
|
||||||
prototype proved representative behavior, not release compliance or model quality.
|
|
||||||
|
|
||||||
The selected PromptKit release uses LGPL-3.0-or-later. A later release unit must
|
|
||||||
review the actual production dependency graph and distribution requirements,
|
|
||||||
including notices and source/rebuild materials, before binary publication. This
|
|
||||||
implementation plan does not claim that review is complete. Release publication
|
|
||||||
uses the existing [procedure](../release.md) and completes on successful source/tag
|
|
||||||
push, without waiting for CI or artifact delivery.
|
|
||||||
@@ -1,392 +0,0 @@
|
|||||||
# Shared Library Boundary: Decisions and Evidence
|
|
||||||
|
|
||||||
**Status:** Boundary and offline prototype completed, 2026-09-23. Selected prototype contract and proposed
|
|
||||||
production migration target; not the current Audita configuration contract.
|
|
||||||
The [bounded roadmap](library-boundary.md) owns scope and completion criteria.
|
|
||||||
|
|
||||||
**Subsequent scope decision:** The [ConfigKit overlay plan](configkit.md#selected-overlays-for-testing)
|
|
||||||
adds optional selected overlays, with v0.4.0 as the migration baseline. It supersedes
|
|
||||||
the original no-overlay decision and adds an input layer between the main file and
|
|
||||||
explicit setting overrides. The v0.3.1 prototype results below remain historical
|
|
||||||
evidence; they do not establish overlay integration.
|
|
||||||
|
|
||||||
## Selected Boundary
|
|
||||||
|
|
||||||
The former prototype is retained in Git history at `590604b`; it was a separate
|
|
||||||
Go module using published ConfigKit v0.4.0, PromptKit v0.11.0, and
|
|
||||||
Audita v1.1.0. The Audita dependency lets this consumer reuse actual internal domain
|
|
||||||
helpers through its Audita module namespace without a local replacement. It does
|
|
||||||
not make those internal packages a new public API. Remove this dependency along
|
|
||||||
with the prototype after equivalent production integration tests landed.
|
|
||||||
|
|
||||||
The initial prototype results below were recorded with ConfigKit v0.3.1. On
|
|
||||||
2026-09-24, upgrading only its ConfigKit pin and sums to v0.4.0 required no source
|
|
||||||
changes. With Go 1.26.8 on linux/amd64, the nested offline `go test ./...` passed
|
|
||||||
and `go run .` still reported one accepted correction, four attempts, zero
|
|
||||||
transient retries, and two structural repairs. This verifies the dependency
|
|
||||||
upgrade, not selected-overlay behavior.
|
|
||||||
|
|
||||||
ConfigKit owns loading, presence, explicit overrides, secret resolution, and
|
|
||||||
redacted effective settings. PromptKit owns profile parsing, rendering, provider
|
|
||||||
execution, transient retries, structural repair, and backend admission. Audita
|
|
||||||
owns file selection, setting meanings, role selection, domain acceptance, and
|
|
||||||
mapping execution facts into reports. Production CLI and runtime remain unchanged.
|
|
||||||
|
|
||||||
## Configuration and Profile Contract
|
|
||||||
|
|
||||||
Selected for the prototype and recommended for production:
|
|
||||||
|
|
||||||
- Main YAML requires integer `version: 2`; absent, null, unknown fields, and older
|
|
||||||
versions fail. No legacy parser, aliases, environment overlays, or automatic
|
|
||||||
conversion. Defaults precede the selected file; explicitly supplied CLI settings
|
|
||||||
become ConfigKit overrides. Validation runs on the resolved result, so an override
|
|
||||||
may repair an invalid lower-precedence setting when decoding succeeds.
|
|
||||||
- Production file selection: explicit `--config`, then
|
|
||||||
`/usr/local/etc/audita/config.yml`, then `/etc/audita/config.yml`. Select one file;
|
|
||||||
do not merge them. Missing system files are skipped; explicit missing or empty
|
|
||||||
paths and inaccessible selected files fail. No selected file fails with a request
|
|
||||||
to configure backend/profile selection: no hidden provider or model default.
|
|
||||||
Remove `AUDITA_CONFIG`. The prototype loads its explicit fixture only; discovery
|
|
||||||
and command parsing belong to the ConfigKit implementation stage.
|
|
||||||
- `profiles_dir` defaults to `profiles`, resolved relative to the selected main
|
|
||||||
configuration file, with absolute paths accepted. PromptKit reads that directory.
|
|
||||||
Prompts and schemas are application-embedded, not operator-selected prompt files.
|
|
||||||
The prototype uses complete application-owned profile files. Catalog profiles and
|
|
||||||
profile inheritance are not needed for this migration; do not add catalog wiring.
|
|
||||||
- `backends.<id>.endpoint` is required. Use one application backend identity per
|
|
||||||
service capacity pool. Do not split proposal and validation into different backend
|
|
||||||
IDs when they use the same service. Limits are engine-local, not shared across
|
|
||||||
multiple Audita processes or other applications.
|
|
||||||
- `backends.<id>.concurrency` defaults to **1**, must be positive, and is shared by
|
|
||||||
both roles and all initial/retry/repair attempts. Operators can select **4** for
|
|
||||||
the described local backend or a suitable finite paid API limit. There is no
|
|
||||||
additional global or per-role limiter. `queue_capacity` defaults to **1024**,
|
|
||||||
following PromptKit; non-negative, with zero meaning no waiting admission.
|
|
||||||
This is an operational escape hatch rather than a second scheduling hierarchy.
|
|
||||||
- `backends.<id>.credential` is an optional ConfigKit secret reference: a scalar
|
|
||||||
environment variable name. Omission or null means no credential. A supplied
|
|
||||||
reference must resolve to a nonempty value, including during validation and
|
|
||||||
inspection. There is no direct key field or CLI key flag. ConfigKit resolves it
|
|
||||||
once and Audita supplies `RunRequest.APIKey`. Profiles must not introduce another
|
|
||||||
`api_key_env` lookup. Backend credentials also support unauthenticated local use.
|
|
||||||
- Profiles own model, temperature, output token limit, top-p, per-generation timeout,
|
|
||||||
service tier, reasoning effort, and extra provider parameters using PromptKit's
|
|
||||||
format. They select the application `backend`; their effective endpoint must
|
|
||||||
equal that backend's endpoint. Endpoint-only execution is rejected because it
|
|
||||||
bypasses the requested finite capacity boundary. A named registered backend is
|
|
||||||
mandatory even if both roles use the same profile.
|
|
||||||
- `llm.proposal_profile` is required. Omitted `llm.validation_profile` inherits the
|
|
||||||
final proposal profile as a whole, with ConfigKit recording the derivation.
|
|
||||||
Explicit empty or null values fail; no individual model/endpoint/key inheritance.
|
|
||||||
- `llm.transient_retries` defaults to **2** additional attempts per generation;
|
|
||||||
explicit zero disables retries; negative is invalid. PromptKit owns which failures
|
|
||||||
are eligible. Do not add an Audita retry loop.
|
|
||||||
- `llm.repair_attempts` defaults to **2** additional generations for both roles;
|
|
||||||
explicit zero disables repair when deliberately requested. PromptKit supports
|
|
||||||
0–3. Positive defaults are exercised in the prototype. Audita owns this common
|
|
||||||
setting and supplies a complete request output contract with the role's embedded
|
|
||||||
schema, avoiding separate user-facing repair defaults in each prompt.
|
|
||||||
- `llm.operation_timeout` defaults to **15m**, must be positive, and uses a Go
|
|
||||||
duration string. It starts before each logical proposal/assessment call and covers
|
|
||||||
preparation, capacity waiting, transient backoff, generation, and repairs. It is
|
|
||||||
separate from whole-process cancellation. Profile `timeout_seconds` defaults to
|
|
||||||
PromptKit's **600** seconds and is applied afresh to each built-in HTTP attempt.
|
|
||||||
It does not bound the combined retry/repair sequence. A stricter caller/transport timeout can
|
|
||||||
also end execution. The two defaults are selected starting points, not throughput
|
|
||||||
measurements. With the default T=2 and R=2, each logical call dispatches at most
|
|
||||||
**9** attempts before deadline/cancellation; proposal plus assessment may use 18.
|
|
||||||
- Omission retains static defaults or invokes the recorded role/backend derivations.
|
|
||||||
Null is not a general synonym for omission: nonoptional scalars reject it. Optional
|
|
||||||
credentials and the future optional section target accept null. Explicit empty
|
|
||||||
module lists will mean an empty correction pipeline, not “restore defaults.”
|
|
||||||
Repeated modules retain their existing ordered meaning.
|
|
||||||
- The original prototype disabled ConfigKit imports, selected configuration layers
|
|
||||||
(then called configuration profiles), and secret-directory bootstrap. Selected
|
|
||||||
overlays are now verified under the linked overlay decision; the other two
|
|
||||||
features remain disabled. PromptKit execution profiles retain their meaning.
|
|
||||||
|
|
||||||
Configuration validation/inspection must assemble and inspect both selected profiles
|
|
||||||
without provider calls. ConfigKit's effective YAML describes only its loaded state;
|
|
||||||
a separate selected-profile view shows effective model controls/backend selection.
|
|
||||||
Do not imply either snapshot alone represents the full execution configuration.
|
|
||||||
The prototype exercises the two APIs but does not implement production subcommands.
|
|
||||||
Do not serialize raw profile/library objects as a permanent Audita report schema.
|
|
||||||
|
|
||||||
## Migration Inventory
|
|
||||||
|
|
||||||
This inventory covers the current configuration and process flags documented in
|
|
||||||
[configuration](../config.md) and [CLI](../cli.md), including environment-only fields.
|
|
||||||
“Retain” below means retain domain meaning in the new schema, not accept old files.
|
|
||||||
All legacy environment overlays listed afterward are removed.
|
|
||||||
|
|
||||||
| Current file field / setting | Current CLI flag(s) | Target and rationale |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| File selection | `--config` | Retain explicit invocation flag; discovery described above. |
|
|
||||||
| Transcript input | positional path | Invocation only; retain one transcript path. |
|
|
||||||
| Glossary input | `--glossary` | Invocation only; retain required glossary path. |
|
|
||||||
| Corrected output | `--output` | Invocation only; retain stdout fallback. |
|
|
||||||
| Report output | `--report-json` | Invocation only; retain separate report file. |
|
|
||||||
| `pipeline.modules` | `--modules` | Retain sequence/default `[glossary, homophones, glossary, spoken_word, grammar]`; explicit empty clears rather than inherits. |
|
|
||||||
| `output.schema` | `--output-schema` | Retain encoder selection/default `bare-segments`. |
|
|
||||||
| `llm.proposal.base_url`, `llm.validation.base_url` | `--base-url`, `--validation-base-url` | Replace with backend endpoint; remove per-role endpoint flags. |
|
|
||||||
| Both `llm.*.model` | `--model`, `--validation-model` | Replace with profile model; new `--proposal-profile` / `--validation-profile` select whole profiles. |
|
|
||||||
| Both `llm.*.api_key_env`, direct runtime keys | `--llm-api-key`, `--validation-llm-api-key` | Replace with backend ConfigKit secret reference; remove direct key flags. |
|
|
||||||
| Both `llm.*.timeout` | `--llm-timeout-seconds`, `--validation-llm-timeout-seconds` | Replace with profile `timeout_seconds`, plus `llm.operation_timeout`; no duplicate timeout flags. |
|
|
||||||
| Both `llm.*.max_retries` | `--max-retries`, `--validation-max-retries` | Replace with common `llm.transient_retries` and distinct `llm.repair_attempts`; remove misleading old flags. |
|
|
||||||
| `concurrency.total_llm` | `--total-llm-concurrency`, `--llm-concurrency` | Replace with per-backend concurrency; remove global/alias flags. |
|
|
||||||
| `concurrency.proposal_llm`, `concurrency.validation_llm` | `--proposal-llm-concurrency`, `--validation-llm-concurrency` | Remove role budgets; shared backend handles service capacity. |
|
|
||||||
| Validation prompt budget (no old YAML field) | `--validation-max-prompt-tokens` | Add `validation.max_prompt_tokens`, default 2048, positive; retain explicit flag override. Audita owns batching. |
|
|
||||||
| `chunking.target_sections` | `--target-sections` | Retain optional positive target; omission/null means automatic, zero invalid. |
|
|
||||||
| `chunking.max_section_tokens` | `--max-section-tokens` | Retain 8192, positive. |
|
|
||||||
| `chunking.min_section_tokens` | `--min-section-tokens` | Retain 2048, positive, no greater than maximum. |
|
|
||||||
| `normalization.max_segment_gap` | `--normalize-max-segment-gap` | Retain 4.0 numeric seconds, non-negative; drop alternate YAML duration syntax for these domain numeric fields. |
|
|
||||||
| `normalization.ellipsis_gap` | `--normalize-ellipsis-gap` | Retain 3.5 numeric seconds, non-negative. |
|
|
||||||
| `normalization.max_segment_duration` | `--normalize-max-segment-duration` | Retain 60.0 numeric seconds, positive. |
|
|
||||||
| `normalization.max_segment_tokens` | `--normalize-max-segment-tokens` | Retain 2048, positive. |
|
|
||||||
| `thresholds.glossary` | `--glossary-confidence-threshold` | Retain 0.8 in [0,1]. |
|
|
||||||
| `thresholds.homophones` | `--homophones-confidence-threshold` | Retain 0.8 in [0,1]. |
|
|
||||||
| `thresholds.spoken_word` | `--spoken-word-confidence-threshold` | Retain 0.8 in [0,1]. |
|
|
||||||
| `thresholds.grammar` | `--grammar-confidence-threshold` | Retain 0.8 in [0,1]. |
|
|
||||||
| `context.description` | `--transcript-description` | Retain trimming and 500-byte limit; empty clears context. |
|
|
||||||
| `diagnostics.work_dir` | `--work-dir` | Retain `/tmp/audita`, nonempty; artifact lifecycle remains Audita-owned. |
|
|
||||||
| `diagnostics.retention` | `--work-dir-retention` | Retain `auto` default and selection; existing `never` follow-up is separate. |
|
|
||||||
| Help/version/config commands | `--help`, `-h` | Retain command purpose; help should parse before loading configuration. Effective-config output intentionally becomes ConfigKit YAML, not legacy JSON. |
|
|
||||||
|
|
||||||
Retained setting flags become explicit ConfigKit overrides only when supplied;
|
|
||||||
CLI parser defaults must not overwrite file values. Normalization flag values stay
|
|
||||||
numeric seconds. Domain values convert into existing domain structs after validation;
|
|
||||||
do not spread ConfigKit or PromptKit types into correction algorithms.
|
|
||||||
|
|
||||||
Complete environment removal map (names remain permissible if explicitly named as
|
|
||||||
secret references; none have implicit configuration meaning):
|
|
||||||
|
|
||||||
| Old environment names | Replacement |
|
|
||||||
| --- | --- |
|
|
||||||
| `AUDITA_CONFIG` | File selection above. |
|
|
||||||
| `AUDITA_MODULES` | `pipeline.modules` / explicit flag. |
|
|
||||||
| `AUDITA_LLM_API_KEY`, `OPENROUTER_API_KEY`, `AUDITA_VALIDATION_LLM_API_KEY` | Explicit backend credential references; no implicit fallback. |
|
|
||||||
| `AUDITA_MODEL`, `AUDITA_VALIDATION_MODEL` | Profile files and role selection. |
|
|
||||||
| `AUDITA_BASE_URL`, `AUDITA_VALIDATION_BASE_URL` | Backend endpoints. |
|
|
||||||
| `AUDITA_LLM_TIMEOUT_SECONDS`, `AUDITA_VALIDATION_LLM_TIMEOUT_SECONDS` | Profile timeout and operation deadline. |
|
|
||||||
| `AUDITA_MAX_RETRIES`, `AUDITA_VALIDATION_MAX_RETRIES` | Common transient/repair settings. |
|
|
||||||
| `AUDITA_TOTAL_LLM_CONCURRENCY`, `AUDITA_LLM_CONCURRENCY`, `AUDITA_PROPOSAL_LLM_CONCURRENCY`, `AUDITA_VALIDATION_LLM_CONCURRENCY` | Backend concurrency; remove alias/role budgets. |
|
|
||||||
| `AUDITA_VALIDATION_MAX_PROMPT_TOKENS` | `validation.max_prompt_tokens` / explicit flag. |
|
|
||||||
| `AUDITA_TARGET_SECTIONS`, `AUDITA_MAX_SECTION_TOKENS`, `AUDITA_MIN_SECTION_TOKENS` | Corresponding chunking fields / flags. |
|
|
||||||
| `AUDITA_NORMALIZE_MAX_SEGMENT_GAP`, `AUDITA_NORMALIZE_ELLIPSIS_GAP`, `AUDITA_NORMALIZE_MAX_SEGMENT_DURATION`, `AUDITA_NORMALIZE_MAX_SEGMENT_TOKENS` | Corresponding normalization fields / flags. |
|
|
||||||
| `AUDITA_GLOSSARY_CONFIDENCE_THRESHOLD`, `AUDITA_HOMOPHONES_CONFIDENCE_THRESHOLD`, `AUDITA_SPOKEN_WORD_CONFIDENCE_THRESHOLD`, `AUDITA_GRAMMAR_CONFIDENCE_THRESHOLD` | Corresponding threshold fields / flags. |
|
|
||||||
| `AUDITA_WORK_DIR`, `AUDITA_WORK_DIR_RETENTION` | Diagnostics fields / flags. |
|
|
||||||
|
|
||||||
## Execution and Failure Mapping
|
|
||||||
|
|
||||||
The executable uses grammar proposal plus grammar review, current response schemas,
|
|
||||||
current hardening text, transcript-section/description helpers, proposal preview, and
|
|
||||||
actual decision-cardinality enforcement from Audita v1.1.0. PromptKit renders the
|
|
||||||
adapted templates; the custom HTTP transport supplies OpenAI-compatible responses.
|
|
||||||
The prototype pre-expands Audita's `hardening` template function into the embedded
|
|
||||||
PromptKit definitions; it passes existing named string variables unchanged. No
|
|
||||||
provider, paid call, or injected high-level LLM client participates.
|
|
||||||
|
|
||||||
Generated JSON is structurally validated/repaired by PromptKit before Audita decodes
|
|
||||||
it. Audita then checks proposal applicability, confidence, and decision identity and
|
|
||||||
cardinality. The demonstration previews corrections without mutation. Production
|
|
||||||
retains every module's full validator chain, glossary protection, batching, and
|
|
||||||
apply-time checks; the representative prototype does not claim to replace them.
|
|
||||||
|
|
||||||
| Outcome | Section/batch effect | Overall run |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Structural repair succeeds | Continue domain validation. | Normal execution. |
|
|
||||||
| Proposal repair exhausted | No proposals from this section; malformed-output warning. | Continue other work; successful completion may include warnings. |
|
|
||||||
| Assessment repair exhausted | Reject this batch; malformed-output warning. | Continue other work. |
|
|
||||||
| Schema-valid invalid/missing/duplicate decision indices | Reject batch and warn; no semantic retry/repair. | Continue other work. |
|
|
||||||
| Inapplicable/low-confidence proposal or negative assessment | Reject affected proposal; preserve domain reasons. | Continue other work. |
|
|
||||||
| Provider failure, retry exhaustion, transport or malformed HTTP response | Stop affected operation; retain available observations. | Runtime failure; cancel remaining work, retain existing Audita diagnostic handling. |
|
|
||||||
| Deadline, caller cancellation, capacity admission failure | Stop affected operation; no outer retry or fabricated output. | Runtime failure with distinct classified cause. |
|
|
||||||
| Invalid settings/profile/schema setup | Fail before processing where detectable. | Configuration error for user input, runtime error for broken embedded assets. |
|
|
||||||
|
|
||||||
Zero transient retries produces one ordinary provider failure, not
|
|
||||||
`ErrRetriesExhausted`; the latter signals an exhausted **positive** budget.
|
|
||||||
Provider failures retain a typed cause. Do not expose raw provider messages through
|
|
||||||
user diagnostics: continue Audita's separate secret-redaction boundary. A failed
|
|
||||||
logical call may return no result while its observations still record completed
|
|
||||||
attempts or an earlier successful generation. Already completed domain results can
|
|
||||||
remain in diagnostics; failure must not be presented as a fully corrected output.
|
|
||||||
|
|
||||||
## Observation and Report Handoff
|
|
||||||
|
|
||||||
The prototype maps library events into Audita-owned records under a short mutex.
|
|
||||||
Records contain logical run/role/backend/profile identity, generation and attempt
|
|
||||||
indices, dispatch state, queue/generation/retry/wall timing, classified outcomes,
|
|
||||||
HTTP status, exhaustion, repair/retry counts, and each usage field's value,
|
|
||||||
knownness, and completeness. It omits URLs, credentials, payloads, raw provider
|
|
||||||
errors, and prompt text. Identifiers themselves are metadata, not a secret channel.
|
|
||||||
|
|
||||||
Use terminal records once for logical totals; retain attempt records for diagnosis
|
|
||||||
without adding them to terminal totals. A known zero is distinct from unknown;
|
|
||||||
partial totals remain incomplete after unmetered failed attempts. These observations
|
|
||||||
are not a billing ledger. Callback code must not wait on its own run's progress.
|
|
||||||
|
|
||||||
Production report changes must distinguish logical calls from dispatched provider
|
|
||||||
attempts. Replace scheduler utilization fields based on global/proposal/validation
|
|
||||||
pools with per-backend admission/active timing. Preserve deterministic ledger and
|
|
||||||
validator ordering. Map prompt definition/rendered hashes and selected profile/model
|
|
||||||
into request metadata, but do not promise equality with legacy prompt hashes or
|
|
||||||
serialize PromptKit's raw event struct as the stable report contract. Update the
|
|
||||||
output integration reference, report mapper tests, and downstream migration guide
|
|
||||||
when the production representation is implemented.
|
|
||||||
|
|
||||||
## Verification and Remaining Handoff
|
|
||||||
|
|
||||||
The focused matrix is implemented in the nested module:
|
|
||||||
|
|
||||||
| Roadmap scenario | Evidence |
|
|
||||||
| --- | --- |
|
|
||||||
| Successful assembled path | `go run .`; `TestWorkflowBoundaries/repair_both_roles`; actual Audita domain helpers and both selected profiles. |
|
|
||||||
| Presence, overrides, inheritance | `TestConfigurationContract`: omitted role/backend values, explicit zero, null/empty, version, unknown keys, and explicit overrides. |
|
|
||||||
| Credentials | Missing/unset and empty references fail; optional null accepted; effective/profile inspection redacted; HTTP authorization verified; no secret/prompt text in observations. `TestProfilesRespectSharedBackendBoundary` rejects competing lookup and capacity bypass. |
|
|
||||||
| Both roles repaired | `repair_both_roles`: four attempts, two repairs, one accepted correction. |
|
|
||||||
| Repair exhausted | Proposal and assessment exhaustion subtests: bounded counts and selected warning/rejection outcomes. |
|
|
||||||
| Semantic boundary | Missing span, missing/duplicate decision indices, negative assessment; no semantic repair loop. |
|
|
||||||
| Retry/zero/exhaustion | 429 recovery, zero-budget 503, positive-budget exhaustion after completed proposal; typed error assertions. |
|
|
||||||
| Combined budgets | 503 → invalid JSON → repair 503 → valid proposal → assessment: five attempts, two transient retries, one repair. |
|
|
||||||
| Shared capacity | `TestSharedCapacityAndRecovery`: caps four and two, both roles, a retry and repair, queue rejection, measured peak equal to cap, later successful call. |
|
|
||||||
| Waiting/cancellation/deadline | `TestQueuedCancellationAndDeadline`; `TestDeadlineStopsRetryAfter`: no queued dispatch after cancellation/deadline and a 60-second provider retry delay interrupted by the operation deadline. |
|
|
||||||
| Failed-run observations | `TestPartialUsageOnFailedRun`: valid HTTP output with known-zero usage followed by repair failure; nil result, retained events, incomplete known aggregate, correct counts. |
|
|
||||||
| Representative assets | `TestRealPromptRenderingParity` compares both rendered message pairs to Audita v1.1.0; `TestSchemaFixturesMatchAudita` compares schema bytes. |
|
|
||||||
|
|
||||||
Commands executed from `examples/library-adoption` on Linux amd64 with Go 1.26.8:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GOWORK=off go mod download all
|
|
||||||
GOWORK=off GOPROXY=off GOSUMDB=off go mod verify
|
|
||||||
GOWORK=off GOPROXY=off GOSUMDB=off go run .
|
|
||||||
GOWORK=off GOPROXY=off GOSUMDB=off go test -race ./...
|
|
||||||
GOWORK=off GOPROXY=off GOSUMDB=off go vet ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
All passed. The demonstration reports accepted=1, rejected=0, attempts=4,
|
|
||||||
transient_retries=0, structural_repairs=2. Module verification reports all modules
|
|
||||||
verified. Published tag availability was checked with `git ls-remote --tags` for
|
|
||||||
ConfigKit v0.3.1 and PromptKit v0.11.0; successful module acquisition verifies this
|
|
||||||
is not merely a local-checkout experiment. The module has no replacements.
|
|
||||||
|
|
||||||
The minimum Go 1.25.5 toolchain was also exercised with offline tests and the same
|
|
||||||
successful demonstration. The exact installed-toolchain invocation was:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GOTOOLCHAIN=local GOWORK=off GOPROXY=off GOSUMDB=off /home/eric/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.5.linux-amd64/bin/go test ./...
|
|
||||||
GOTOOLCHAIN=local GOWORK=off GOPROXY=off GOSUMDB=off /home/eric/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.5.linux-amd64/bin/go run .
|
|
||||||
```
|
|
||||||
|
|
||||||
An initial attempt to select that cached toolchain through `GOTOOLCHAIN=go1.25.5`
|
|
||||||
with checksum verification disabled was rejected by Go's toolchain verifier. Using
|
|
||||||
the installed binary with `GOTOOLCHAIN=local` proved offline execution without
|
|
||||||
weakening dependency checks. The portable README commands assume an installed
|
|
||||||
sufficiently new Go executable on PATH and disable toolchain switching.
|
|
||||||
|
|
||||||
From the Audita root, `go test ./...` and
|
|
||||||
`go build -o ./bin/audita ./cmd/audita` passed. These check existing production code;
|
|
||||||
they do not cover the nested module. Affected relative documentation links,
|
|
||||||
inventory coverage, and `git diff --check` were checked separately.
|
|
||||||
|
|
||||||
### Findings and Limits
|
|
||||||
|
|
||||||
No adoption-blocking upstream API gap was found. Two integration details deserve
|
|
||||||
explicit attention: PromptKit's profile GoDoc calls `timeout_seconds` a generation
|
|
||||||
deadline, but its built-in HTTP path applies it separately to each attempt; the
|
|
||||||
consumer therefore needs the selected total-operation deadline. Also,
|
|
||||||
`APIKeyRequired` is an in-memory profile facility, not a profile-YAML field. The
|
|
||||||
file-profile design uses the presence of ConfigKit's optional credential reference
|
|
||||||
to require local secret resolution; it does not invent a new PromptKit YAML key.
|
|
||||||
|
|
||||||
The chosen defaults are proposed operational policy, not measured model-quality or
|
|
||||||
cost recommendations. Tests use controlled transport responses and synthetic data;
|
|
||||||
they do not establish real provider compatibility, model quality, network behavior,
|
|
||||||
throughput, cross-process coordination, complete billing, or release compliance.
|
|
||||||
Only the representative LLM-facing subset is implemented in the prototype; the
|
|
||||||
inventory specifies the remaining domain fields for the ConfigKit migration.
|
|
||||||
Production file discovery, CLI parsing, full module chains, all nine prompts,
|
|
||||||
filesystem diagnostics/redaction, and serialized report migration remain their
|
|
||||||
assigned production tasks. Those are deliberate scope boundaries, not missing
|
|
||||||
claims of completion for the prototype.
|
|
||||||
|
|
||||||
The new profile/credential/capacity and deadline choices are explicit above; no
|
|
||||||
unresolved upstream requirement prevents starting the ConfigKit implementation
|
|
||||||
plan. Production report field names and final runtime wiring are selected in the
|
|
||||||
[implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md), following the outcome and observation
|
|
||||||
contract here. The coordinated refactor targets both libraries directly; intermediate commits do not require a working legacy runtime.
|
|
||||||
|
|
||||||
### Production Implementation Reference
|
|
||||||
|
|
||||||
The [implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md) owns ordered production work,
|
|
||||||
component changes, tests, and acceptance criteria. The [combined roadmap](library-adoption.md)
|
|
||||||
and [configuration roadmap](configkit.md) own the final target. This evidence does
|
|
||||||
not require an interim runtime bridge or separately runnable configuration migration.
|
|
||||||
|
|
||||||
Retained domain scope includes transcript/glossary schemas, normalization/chunking,
|
|
||||||
output encoding, proposals and apply-time checks, deterministic validators, ordered
|
|
||||||
modules, LLM-validator batching/cardinality, ledger ordering, and artifact lifecycle.
|
|
||||||
The production integration replaces loading, provider execution, prompt rendering,
|
|
||||||
and scheduler composition while preserving those responsibilities. Retention and
|
|
||||||
timestamp changes remain separate work.
|
|
||||||
|
|
||||||
### Distribution Follow-Up
|
|
||||||
|
|
||||||
The pinned graph includes ConfigKit, PromptKit, both PromptKit backend catalogs,
|
|
||||||
jsonschema/v6, yaml.v3, yaml.v4, x/text, and kr/text (including module/test dependency
|
|
||||||
edges). Before distribution, review the actual production dependency graph and
|
|
||||||
its license files, including PromptKit/catalog LGPL-3.0-or-later terms. Decide and
|
|
||||||
verify the notices, license copies, corresponding library source/modifications,
|
|
||||||
and rebuild/relink materials needed for Audita's Go binary/container distribution.
|
|
||||||
This unit does not publish binaries or claim that release compliance is complete.
|
|
||||||
No upstream relicensing request or library implementation change is required for
|
|
||||||
this prototype.
|
|
||||||
|
|
||||||
## Selected-Overlay Extension: 2026-09-24
|
|
||||||
|
|
||||||
The isolated prototype now pins published ConfigKit v0.4.0. ConfigKit loads
|
|
||||||
`overlays/testing.yml` relative to the selected main file; the overlay selects
|
|
||||||
complete PromptKit execution profiles from the separate `profiles/` directory.
|
|
||||||
The original v0.3.1 observations above are historical and do not prove this
|
|
||||||
extension. No production Audita behavior changed.
|
|
||||||
|
|
||||||
With Go 1.26.8 on linux/amd64, from `examples/library-adoption/`, these offline
|
|
||||||
checks passed with `GOTOOLCHAIN=local GOWORK=off GOPROXY=off GOSUMDB=off`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go test ./...
|
|
||||||
go test -race ./...
|
|
||||||
go vet ./...
|
|
||||||
go run .
|
|
||||||
go run . --overlay testing
|
|
||||||
go run . --overlay testing --proposal-profile proposal
|
|
||||||
```
|
|
||||||
|
|
||||||
Each demonstration reported one accepted correction, four attempts, zero
|
|
||||||
transient retries, and two structural repairs. The base run selected
|
|
||||||
`synthetic-grammar`/`synthetic-review`; testing selected
|
|
||||||
`synthetic-grammar-testing`/`synthetic-review-testing`; the explicit proposal
|
|
||||||
override restored only `synthetic-grammar`. These are synthetic routing IDs,
|
|
||||||
not price, latency, quality, or provider-compatibility measurements.
|
|
||||||
|
|
||||||
| Requirement | Consumer evidence |
|
|
||||||
| --- | --- |
|
|
||||||
| Base, empty/default, testing selection, role overrides, final inheritance, and relative profile resolution | `TestSelectedOverlayConfiguration` loads temporary on-disk layouts and assembles both roles. |
|
|
||||||
| Actual base/testing/override HTTP models and repairs | `TestDemonstrationSelectsOutboundModels` captures every request body and matches model IDs to the reported selections and domain outcome. |
|
|
||||||
| Missing/invalid selection, version/directive rejection, and ordinary backend-setting validation | `TestOverlayFailuresAndApplicationValidation` checks ConfigKit diagnostic codes and effective backend limits. |
|
|
||||||
| Surviving credential resolution, absent/empty failures, null unauthenticated use, and profile-only switching | `TestOverlayCredentialResolutionAndInspection` controls synthetic environment values and observes HTTP Authorization. |
|
|
||||||
| Redacted effective output, explanation and metadata origin, profile inspection, and observations | `TestOverlayCredentialResolutionAndInspection` and `TestOverlaySelectionProvenance` inspect safe output and overlay/CLI origins. |
|
|
||||||
| Existing retry, repair, domain, capacity, and concurrent-observation behavior | `TestWorkflowBoundaries`, `TestSharedCapacityAndRecovery`, `TestQueuedCancellationAndDeadline`, `TestDeadlineStopsRetryAfter`, and `TestPartialUsageOnFailedRun` pass, including under `-race`. |
|
|
||||||
|
|
||||||
Changing a profile does not remove a backend credential reference from the
|
|
||||||
effective configuration. ConfigKit validates a surviving reference when only
|
|
||||||
profile selection changes. Replacing the reference in an
|
|
||||||
overlay resolves only the replacement; explicit null allows unauthenticated use.
|
|
||||||
This distinction is part of the selected configuration boundary, not an
|
|
||||||
application fallback. Imports and secret-directory bootstrap remain disabled.
|
|
||||||
|
|
||||||
The tests use a controlled built-in HTTP transport and synthetic credentials;
|
|
||||||
there were no live provider calls. The prototype still covers one representative
|
|
||||||
proposal/assessment pair, not all production modules, prompts, CLI paths, or
|
|
||||||
diagnostic artifacts. Root `go test ./...` does not run this nested module. No
|
|
||||||
upstream API gap appeared in this extension. The [production implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md)
|
|
||||||
keeps implementation and release pending.
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
# Library Adoption: Shared Boundary and Prototype
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-24
|
|
||||||
|
|
||||||
**Status:** Original boundary-design and offline-prototype unit completed;
|
|
||||||
overlay extension and production-planning handoff completed, 2026-09-24.
|
|
||||||
The work packages below retain the original scope;
|
|
||||||
[decisions, evidence, limitations, and handoff](library-boundary-findings.md)
|
|
||||||
record the outcome. The executable prototype was removed after production
|
|
||||||
integration; Git history at `590604b` retains it. No publication or upstream
|
|
||||||
changes were made in that historical unit.
|
|
||||||
|
|
||||||
## Next Unit: Testing Overlays and Production Migration Plan
|
|
||||||
|
|
||||||
**Status:** Complete. The [production implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/cb405f6/docs/roadmap/implementation.md)
|
|
||||||
owns subsequent work; the [overlay findings](library-boundary-findings.md#selected-overlay-extension-2026-09-24)
|
|
||||||
record this unit's checks. The original work packages below remain
|
|
||||||
historical scope for the completed first unit.
|
|
||||||
|
|
||||||
Extend the existing prototype to prove the [selected-overlay contract](configkit.md#selected-overlays-for-testing)
|
|
||||||
with ConfigKit v0.4.0, then turn the verified findings into a detailed production
|
|
||||||
ConfigKit migration plan. Do not repeat the original inventory or redesign the
|
|
||||||
established library boundary without new evidence.
|
|
||||||
|
|
||||||
Required work:
|
|
||||||
|
|
||||||
- Upgrade only the nested prototype module from ConfigKit v0.3.1 to v0.4.0.
|
|
||||||
- Pass an explicit overlay selection through ConfigKit and add `overlays/testing.yml`
|
|
||||||
plus two PromptKit testing profiles. Select both roles and retain one shared
|
|
||||||
finite backend. Synthetic model names demonstrate routing; they do not benchmark
|
|
||||||
real model cost, speed, or quality.
|
|
||||||
- Make the offline executable accept overlay and explicit role-profile selections,
|
|
||||||
and show the selected models alongside domain outcomes and retry/repair counts.
|
|
||||||
- Verify base/overlay/CLI precedence, role inheritance, invalid selections,
|
|
||||||
surviving credential references, safe inspection, and overlay provenance.
|
|
||||||
Prove the actual HTTP request models for both roles; loading changed strings
|
|
||||||
alone is insufficient evidence.
|
|
||||||
- Record new results separately from the historical v0.3.1 findings. Expand the
|
|
||||||
existing ConfigKit roadmap into a concrete production implementation handoff,
|
|
||||||
including final runtime integration and removal of legacy configuration.
|
|
||||||
|
|
||||||
Target end state: reviewers can run the prototype with and without the testing
|
|
||||||
selection and see both LLM roles switch models. The offline test suite establishes
|
|
||||||
that selection, inspection, and execution agree, and the production ConfigKit work
|
|
||||||
has bounded tasks with explicit acceptance criteria.
|
|
||||||
|
|
||||||
Acceptance criteria:
|
|
||||||
|
|
||||||
- The published v0.4.0 dependency is pinned without local replacements; the original
|
|
||||||
no-overlay path and existing integration tests continue to pass.
|
|
||||||
- `testing` switches both roles, explicit profile flags override its selections,
|
|
||||||
and omission/empty/`default` retains base selection according to ConfigKit.
|
|
||||||
- Invalid overlays fail clearly; inheritance and secret resolution use surviving
|
|
||||||
effective settings. Inspection records overlay origin without disclosing keys.
|
|
||||||
- Both selected testing models reach the controlled HTTP transport; structural
|
|
||||||
repair, shared capacity, observations, and Audita domain checks remain active.
|
|
||||||
- Updated examples, executable instructions, findings, and a detailed production
|
|
||||||
migration plan are reviewable. Exact verification commands and limitations are
|
|
||||||
recorded, including the fact that root tests do not cover the nested module.
|
|
||||||
- Production code, root dependencies, and upstream libraries remain unchanged.
|
|
||||||
No release or live provider call is part of this unit.
|
|
||||||
|
|
||||||
This unit ends with a production migration **plan**, not production implementation.
|
|
||||||
The ConfigKit → PromptKit → combined release sequence remains unchanged. Imports
|
|
||||||
and secret-directory bootstrap remain disabled; PromptKit execution profiles keep
|
|
||||||
their existing names and meaning.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
Define and prove the boundary between Audita, ConfigKit, and PromptKit before
|
|
||||||
replacing Audita's production configuration or LLM runtime. This unit covers the
|
|
||||||
shared design and representative prototype supporting the
|
|
||||||
[coordinated adoption roadmap](library-adoption.md). The
|
|
||||||
[ConfigKit roadmap](configkit.md) continues to own the broader configuration scope.
|
|
||||||
|
|
||||||
The deliverable is a concrete target contract supported by an offline executable
|
|
||||||
prototype, sufficient to implement the ConfigKit migration without subsequently
|
|
||||||
redesigning its LLM-facing settings. A successful prototype is not completion of
|
|
||||||
either production migration.
|
|
||||||
|
|
||||||
## Established Direction
|
|
||||||
|
|
||||||
- This is an intentional breaking migration with a migration guide, not legacy
|
|
||||||
configuration support, aliases, or a permanent compatibility mode.
|
|
||||||
- ConfigKit owns shared configuration mechanics; PromptKit owns shared prompt and
|
|
||||||
generation mechanics; Audita retains domain meaning and transcript correction.
|
|
||||||
- Structural repair is enabled from the outset for proposal and LLM-validator calls.
|
|
||||||
Positive default repair budgets must be selected, not silently left at zero.
|
|
||||||
- Transient retries and structural repair are separate. Explicit zero disables
|
|
||||||
transient retries; Audita must not add a second automatic retry loop.
|
|
||||||
- One finite concurrency limit per backend protects local capacity and bounds paid
|
|
||||||
API activity. Calls from both roles share that limit. Fine-grained role budgets
|
|
||||||
and an additional global concurrency control are unnecessary.
|
|
||||||
- Keep the sequence: boundary/prototype, ConfigKit migration, PromptKit migration,
|
|
||||||
then combined major release. No intermediate public release is planned.
|
|
||||||
|
|
||||||
## Scope and Deliverables
|
|
||||||
|
|
||||||
| Deliverable | Required result |
|
|
||||||
| --- | --- |
|
|
||||||
| Configuration inventory and migration map | Every existing setting and invocation option has a proposed destination, replacement, or removal, with rationale. |
|
|
||||||
| Boundary specification | One authoritative source for each setting, explicit precedence/defaults, credential flow, runtime conversion, and failure handling. |
|
|
||||||
| Representative executable prototype | New-style configuration drives one proposal and one assessment through released libraries and a controlled provider. |
|
|
||||||
| Focused behavioral evidence | Retry, repair, capacity, credential, observation, and domain-boundary scenarios below are demonstrated. |
|
|
||||||
| Decision and integration handoff | Concrete choices, measured limitations, affected components, removal plan, and next implementation tasks are recorded. |
|
|
||||||
|
|
||||||
Do not switch the production CLI to the prototype, migrate every module/prompt,
|
|
||||||
redesign transcript/glossary formats, change correction algorithms, modify retention
|
|
||||||
or timestamp behavior, or publish a release in this unit. Do not reproduce the
|
|
||||||
libraries' full test suites. Upstream changes require a separately scoped task if
|
|
||||||
an actual gap is found.
|
|
||||||
|
|
||||||
## Work Package 1: Inventory and Decide Ownership
|
|
||||||
|
|
||||||
Read the current [configuration](../config.md), [CLI](../cli.md),
|
|
||||||
[LLM runtime](../internal/llm-runtime.md), [prompts](../internal/prompts.md),
|
|
||||||
[validators](../internal/validators.md), and [output contracts](../integrations/output-files.md).
|
|
||||||
Use graph-first discovery to verify the relevant code and tests, especially
|
|
||||||
configuration application, prompt construction, structured responses, scheduling,
|
|
||||||
and report assembly.
|
|
||||||
|
|
||||||
Build a migration table grouped by application/domain settings, invocation state,
|
|
||||||
backend settings, execution profiles, output contracts, and inspection. Include
|
|
||||||
current environment settings and aliases without treating their existence as a
|
|
||||||
requirement to preserve them. Identify report fields whose meanings change when
|
|
||||||
one logical generation contains multiple provider attempts.
|
|
||||||
|
|
||||||
Resolve these design decisions explicitly:
|
|
||||||
|
|
||||||
| Decision | Proposed direction to prove |
|
|
||||||
| --- | --- |
|
|
||||||
| File selection and version | Audita selects the main configuration and validates a new schema version; specify discovery and the treatment of old versions. |
|
|
||||||
| Application versus invocation state | ConfigKit loads persistent settings; Audita parses invocation-only input/output paths and translates explicitly supplied setting flags into supported overrides. |
|
|
||||||
| Backend identity | One registration represents the service capacity shared by proposal and validation calls. Avoid separate identities for the same service merely because roles differ. |
|
|
||||||
| Endpoint and capacity ownership | Define them once per backend, with finite limits and an explicit policy for waiting capacity/exhaustion. Do not expose endpoint-only unlimited execution accidentally. |
|
|
||||||
| Execution profiles | Prefer complete application-owned profiles using PromptKit conventions. Decide their source and selection without duplicating model/settings fields in application configuration. Catalog selection remains a deliberate option. |
|
|
||||||
| Role selection and inheritance | Specify how proposal and validation choose profiles, including omission and any inheritance. Do not inherit old behavior solely for compatibility. |
|
|
||||||
| Credentials | Prefer one ConfigKit-owned secret-resolution path passed through PromptKit's supported request credential field. Define backend/profile credential association and avoid competing environment lookups. Validate this against profile requirements. |
|
|
||||||
| Retry, repair, and deadlines | Assign one owner and explicit defaults for each control. Use positive structural-repair defaults and document combined limits and total-operation scope. |
|
|
||||||
| Optional library features | The original prototype omitted imports, selected configuration layers, and secret-directory bootstrap. Selected overlays are now planned for testing; see the [overlay plan](configkit.md#selected-overlays-for-testing). Imports and secret-directory bootstrap remain out. |
|
|
||||||
| Inspection | Define effective configuration and selected-profile inspection without implying that one library's snapshot contains the other's complete state. |
|
|
||||||
|
|
||||||
Choose concrete field names, example values, defaults, units, and omission/null/zero
|
|
||||||
semantics for the prototype. In particular, decide what validation and inspection
|
|
||||||
do when configured credentials are unavailable. Classify choices as proposed,
|
|
||||||
selected, or unresolved; do not present candidates as implemented contracts.
|
|
||||||
|
|
||||||
## Work Package 2: Specify Execution and Reporting Semantics
|
|
||||||
|
|
||||||
Document the flow from selected configuration through backend/profile assembly,
|
|
||||||
prompt inputs, generation, structural validation/repair, typed decoding, and Audita
|
|
||||||
domain validation. Identify which values cross the boundary and which component
|
|
||||||
owns each conversion. Keep library types out of unrelated domain code where a
|
|
||||||
small existing domain value is sufficient.
|
|
||||||
|
|
||||||
Use the current correction-set and validator-decision schemas as representative
|
|
||||||
inputs. Reuse domain checks where practical. Select one real proposal/validator
|
|
||||||
pair; account for prompt helpers, template variables, shared fragments, and schema
|
|
||||||
references rather than testing only an unrelated hello-world prompt.
|
|
||||||
|
|
||||||
Record the following distinctions:
|
|
||||||
|
|
||||||
- Repair seeks output conforming to its structural contract. Success does not
|
|
||||||
establish semantic correctness or authorize applying a correction.
|
|
||||||
- A schema-valid decision set still requires Audita's cardinality and proposal
|
|
||||||
identity checks. Semantic rejection is not automatically another repair request.
|
|
||||||
- Exhausted structural repair yields failed content validation; provider failures,
|
|
||||||
retry exhaustion, cancellation, and capacity rejection have separate outcomes.
|
|
||||||
- Specify each outcome's intended effect on an Audita section/batch and overall run,
|
|
||||||
including warnings, rejected work, partial domain results, and terminal failures.
|
|
||||||
Preserve domain intent without preserving obsolete transport behavior by accident.
|
|
||||||
- For transient retry budget T and structural repair budget R, one logical call can
|
|
||||||
dispatch up to `(T + 1) * (R + 1)` provider attempts. For example, T=2 and R=2
|
|
||||||
permits nine attempts; these illustrative values do not select production defaults.
|
|
||||||
- Choose the total-operation deadline scope for a logical proposal or assessment;
|
|
||||||
distinguish it from per-attempt timeouts and whole-process cancellation. Provider
|
|
||||||
retry timing may exceed local backoff, so deadline behavior must be explicit.
|
|
||||||
|
|
||||||
Design the Audita observation record and aggregation rules. Include logical-call
|
|
||||||
and attempt identity, role/backend correlation, queue/generation/retry timing,
|
|
||||||
repair and retry counts, outcomes, and available usage. Preserve unknown/incomplete
|
|
||||||
usage rather than inventing zeros or complete costs. Do not add terminal totals to
|
|
||||||
attempt totals. Collect events safely across concurrent calls, without blocking
|
|
||||||
callbacks on progress by the same run or persisting raw library events as a stable
|
|
||||||
Audita schema. Transcript-bearing artifacts remain separately governed by Audita.
|
|
||||||
|
|
||||||
## Work Package 3: Build the Bounded Offline Prototype
|
|
||||||
|
|
||||||
Use ConfigKit v0.3.1 and PromptKit v0.11.0 as reviewed starting points; verify their
|
|
||||||
published availability and APIs before pinning the prototype. Recheck the combined
|
|
||||||
Go minimum (the reviewed PromptKit release requires Go 1.25.5). Do not depend on
|
|
||||||
sibling checkouts, an active Go workspace, or committed local module replacements.
|
|
||||||
|
|
||||||
A proposed home is `examples/library-adoption/` as an explicitly experimental,
|
|
||||||
standalone Go module, isolating prototype dependencies from the production binary.
|
|
||||||
Confirm this placement during execution. Its README must give exact offline run
|
|
||||||
and test commands after dependency acquisition; root `go test ./...` must not be
|
|
||||||
claimed to cover a nested module. Keep complete proposed configuration and prompt
|
|
||||||
fixtures with it and clearly label them as prototype-only.
|
|
||||||
|
|
||||||
The prototype should:
|
|
||||||
|
|
||||||
1. Load representative application settings with ConfigKit, using synthetic secret
|
|
||||||
values supplied by the test environment and an application-owned version rule.
|
|
||||||
2. Assemble shared backend registrations and proposal/validation profile selections.
|
|
||||||
3. Supply representative transcript/proposal inputs to PromptKit definitions and
|
|
||||||
actual response schemas using supported sources.
|
|
||||||
4. Use the built-in HTTP generation path with a controlled local endpoint or
|
|
||||||
transport. An injected LLM client alone cannot prove built-in HTTP retry behavior.
|
|
||||||
5. Run proposal generation, decode and check candidates, run assessment, and perform
|
|
||||||
the selected Audita decision checks without modifying real transcript files.
|
|
||||||
6. Collect selected safe observations and show a compact result understandable to a
|
|
||||||
reviewer, including repaired output and final domain acceptance/rejection.
|
|
||||||
|
|
||||||
No paid calls, real credentials, host environment assumptions, persistent caches,
|
|
||||||
or general-purpose test framework are needed. Make concurrency and cancellation
|
|
||||||
checks deterministic through controlled synchronization. Keep delays bounded and
|
|
||||||
use library-supported controls; do not patch library internals to make tests pass.
|
|
||||||
|
|
||||||
## Focused Verification Matrix
|
|
||||||
|
|
||||||
| Scenario | Evidence required |
|
|
||||||
| --- | --- |
|
|
||||||
| Successful assembled path | ConfigKit-loaded selections drive both real prompt roles through PromptKit and Audita checks. |
|
|
||||||
| Presence, overrides, and inheritance | Representative omission, explicit values, and role selection follow the proposed contract; unknown fields and unsupported versions fail clearly. |
|
|
||||||
| Credentials | Required missing/empty references fail as intended; controlled calls receive the chosen synthetic credential; inspection and observations do not expose it. |
|
|
||||||
| Structural repair succeeds | Invalid proposal output and invalid assessment output are repaired in their respective workflows; positive budgets are actually exercised. |
|
|
||||||
| Repair exhausted | Repeated invalid output produces the selected Audita outcome and accurate accounting, without an unbounded outer retry. |
|
|
||||||
| Domain rejection | Structurally valid but unacceptable correction or decision is rejected by Audita, demonstrating the semantic boundary. |
|
|
||||||
| Transient retry and explicit zero | An eligible temporary status recovers; zero makes one attempt. Exhaustion retains a useful classified cause. |
|
|
||||||
| Combined repair and retry | A repaired generation encountering a transient failure respects both budgets and reports distinct counts. |
|
|
||||||
| Shared capacity | Concurrent calls from both roles, including retry/repair activity, stay within one selected backend cap; test a representative local cap of four and a finite API cap. |
|
|
||||||
| Waiting, cancellation, and deadline | Cancellation/deadline stops pending work and retry waits; capacity exhaustion has the intended application outcome and later calls can still proceed. |
|
|
||||||
| Failed-run observations | Completed work remains observable after later failure; unknown usage, known zero, and aggregate counts are mapped correctly. |
|
|
||||||
|
|
||||||
Tests should protect Audita's integration decisions and important assembled behavior.
|
|
||||||
Rely on PromptKit's tests for exhaustive HTTP-status, backoff, and scheduler details;
|
|
||||||
rely on ConfigKit's tests for its parser and presence machinery. Use race testing for
|
|
||||||
new concurrent observation collection. Keep consumer checks offline and credential-free.
|
|
||||||
|
|
||||||
## Completion Criteria and Handoff
|
|
||||||
|
|
||||||
This unit is complete only when:
|
|
||||||
|
|
||||||
- The settings/ownership table and migration inventory are concrete enough to start
|
|
||||||
the ConfigKit migration, including file/profile sources, CLI scope, credentials,
|
|
||||||
defaults, finite backend limits, positive repair budgets, and deadline policy.
|
|
||||||
- The offline prototype and focused matrix demonstrate the selected boundary with
|
|
||||||
both libraries together, including both LLM roles and Audita domain checks.
|
|
||||||
- Exact verification commands, results, limitations, and unresolved upstream gaps
|
|
||||||
are recorded. Missing evidence is not reported as successful integration.
|
|
||||||
- No adoption-critical decision remains hidden in prototype code. If a gap prevents
|
|
||||||
settling the boundary, record it and revise the plan before declaring readiness.
|
|
||||||
- The existing Audita production behavior remains intact in this unit. Temporary
|
|
||||||
code is clearly isolated and has a removal/integration destination during the
|
|
||||||
production migrations; no second supported runtime is introduced.
|
|
||||||
- A component-level follow-up identifies code to replace, domain code to retain,
|
|
||||||
tests to update, documentation/migration-guide work, and direct integration of
|
|
||||||
the final configuration and PromptKit runtime.
|
|
||||||
- Licensing/distribution follow-up for the selected dependency graph is identified
|
|
||||||
for the later release stage; this prototype does not claim release compliance.
|
|
||||||
- Roadmap links and examples resolve, appropriate checks pass, and commits contain
|
|
||||||
only this unit's work. Significant settled architectural choices may be recorded
|
|
||||||
in an ADR under the [documentation policy](../policy/documentation.md), while
|
|
||||||
implementation status stays in the roadmaps.
|
|
||||||
|
|
||||||
Retain the overall adoption order unless evidence or the other planned large changes
|
|
||||||
justifies revising it. Completing this unit enables detailed production migration;
|
|
||||||
it does not authorize an intermediate release or require unrelated runtime fixes.
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
# Standard Policy Alignment
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-28
|
|
||||||
|
|
||||||
**Status:** Implemented. This roadmap replaces its earlier licensing-only scope
|
|
||||||
and records the accepted end state. [implementation.md](implementation.md)
|
|
||||||
records the ordered work and verification; the adopted policies are in
|
|
||||||
`docs/policy/`.
|
|
||||||
|
|
||||||
## Purpose and Review Revision
|
|
||||||
|
|
||||||
Align Audita with [Standard Repository Policies](https://gitea.maximumdirect.net/eric/policy),
|
|
||||||
using identical policy language except where a substantive, locally significant
|
|
||||||
requirement or decision justifies a variation. Adopt upstream wording verbatim
|
|
||||||
for non-substantive differences. Perform the required template customizations
|
|
||||||
without inventing application contracts, restrictions, or guarantees.
|
|
||||||
|
|
||||||
The accepted review compared Audita's current documents directly against upstream
|
|
||||||
commit **`d84a38ebab26f048cafbe59a2246700c35d29843`**, beginning with the
|
|
||||||
[adoption and update guide at that revision](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/adoption.md).
|
|
||||||
Use that revision for this adoption. Historical adoption baselines need not be
|
|
||||||
reconstructed or assessed. A later upstream revision requires deliberate review,
|
|
||||||
not an automatic substitution during implementation.
|
|
||||||
|
|
||||||
The public upstream repository, discovery links, and copied-policy attribution
|
|
||||||
are part of the accepted direction. This supersedes the earlier instruction to
|
|
||||||
omit references to the policy repository.
|
|
||||||
|
|
||||||
## Current Baseline
|
|
||||||
|
|
||||||
- Audita has five adopted policies: agents 1.0, architecture 1.0, documentation
|
|
||||||
1.1, testing 1.0, and release 1.0. None has per-document `Upstream Source`
|
|
||||||
metadata. `meta.md` and `oss.md` are absent.
|
|
||||||
- [AGENTS.md](../../AGENTS.md) routes readers to the contributor guide and agent
|
|
||||||
policy. [docs/development.md](../development.md) has component-specific reading
|
|
||||||
guidance, but lacks the upstream selective Policy Reading Guide and currently
|
|
||||||
directs all tasks to the architecture policy.
|
|
||||||
- [COPYRIGHT](../../COPYRIGHT) and [LICENSE](../../LICENSE) already establish
|
|
||||||
Audita's GPL-3.0-or-later application grant and full GPLv3 text. The grant does
|
|
||||||
not yet identify the scope of separately licensed copied policy documents.
|
|
||||||
- There is no canonical `docs/oss/attribution.md` or local dependency license-text
|
|
||||||
collection. Relevant component notices still require an evidence-based inventory.
|
|
||||||
- The [README](../../README.md) lacks the prescribed final licensing statements.
|
|
||||||
The [release procedure](../release.md) lacks licensing-footer instructions, and
|
|
||||||
the [release workflow](../../.woodpecker/release.yml) does not explicitly reuse
|
|
||||||
checked-in release notes as hosted release bodies.
|
|
||||||
- Audita already releases through one publication command with an atomic source
|
|
||||||
and tag push. Linux amd64 and arm64 are required; macOS arm64 is best-effort
|
|
||||||
development support. Successful publication completes the release without
|
|
||||||
waiting for CI or artifact delivery.
|
|
||||||
|
|
||||||
## Targeted Policy Set and Justified Customizations
|
|
||||||
|
|
||||||
All seven policy files belong under `docs/policy/`. The versions below are those
|
|
||||||
at the selected upstream revision. No existing policy is wholly unchanged;
|
|
||||||
existing architectural decisions and platform commitments need no substantive
|
|
||||||
revision merely to align the text.
|
|
||||||
|
|
||||||
| Policy | Target version | Required alignment and justified local treatment |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `agents.md` | 1.3 | Adopt upstream substantive language verbatim. Retain Audita's graph-first Codebase Discovery section because it is an explicitly selected repository workflow. |
|
|
||||||
| `architecture.md` | 1.3 | Adopt general ownership, boundary, and constraint guidance, then perform the template's required application-specific customization. Preserve Audita's ordered pipeline, ConfigKit/PromptKit responsibilities, deterministic processing, recovery, concurrency, and secret-handling decisions. These describe real project requirements rather than stylistic preferences. |
|
|
||||||
| `documentation.md` | 1.11 | Adopt upstream prose and applicable ownership rows. Retain concrete output-contract ownership, contributor change recipes, and the decision to place operating/recovery guidance in operations instead of restoring troubleshooting documentation. Omit HTTP API and public Go API ownership rows because Audita has neither responsibility. |
|
|
||||||
| `testing.md` | 1.3 | Adopt substantive text verbatim. Its distinction between established contracts and invented test requirements needs no local exception. |
|
|
||||||
| `release.md` | 1.4 | Adopt upstream substantive language. Preserve Linux amd64/arm64 requirements, best-effort macOS arm64, unsupported Windows/macOS amd64, and Audita's explicit decision against human release gates. These are selected support and workflow commitments. |
|
|
||||||
| `meta.md` | 1.0 | Adopt provenance, local authority, and public discovery guidance with accurate metadata and local attribution navigation. No additional substantive exception is needed. |
|
|
||||||
| `oss.md` | 1.8 | Adopt the House Format and maintenance rules, with required project-specific facts and reference links. Preserve Audita's GPL-3.0-or-later application grant, Gitea host, and existing distribution channels. |
|
|
||||||
|
|
||||||
Remove each `Base Policy Template` section after carrying out its instructions.
|
|
||||||
Keep local customizations identifiable and explain their project-specific reason
|
|
||||||
in the appropriate policy; do not maintain alternate paraphrases of shared rules.
|
|
||||||
Architecture customization does not authorize a package redesign or additional
|
|
||||||
threat-model guarantees. Supporting documents are warranted by existing
|
|
||||||
responsibilities and substantive content, not by unused template examples.
|
|
||||||
|
|
||||||
Successful-tag-push completion, asynchronous artifacts, minimal automated gates,
|
|
||||||
and proportionate validation are now shared upstream rules. Replace Audita's
|
|
||||||
parallel explanations with the upstream language rather than describing these
|
|
||||||
as continuing exceptions. Keep concrete commands, remotes, build targets, and
|
|
||||||
publication mechanics in the release procedure with appropriate policy links.
|
|
||||||
|
|
||||||
## Provenance and Selective Reading
|
|
||||||
|
|
||||||
For every adopted policy, retain the selected upstream `Base Policy Version`,
|
|
||||||
set `Last Updated` to its actual local adoption date, and introduce `Upstream
|
|
||||||
Source` linking to that exact file at the selected full commit. The URL pattern is:
|
|
||||||
|
|
||||||
```text
|
|
||||||
https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/<filename>.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Substitute each of `agents`, `architecture`, `documentation`, `testing`, `release`,
|
|
||||||
`meta`, and `oss`; a repository-root or shared generic source link is insufficient.
|
|
||||||
These links describe the source actually incorporated, not a reconstructed claim
|
|
||||||
about older local documents. Git and per-document metadata are sufficient; no
|
|
||||||
separate synchronization registry or recurring check is required.
|
|
||||||
|
|
||||||
In `meta.md`, retain these public discovery destinations:
|
|
||||||
|
|
||||||
- [Standard Repository Policies](https://gitea.maximumdirect.net/eric/policy).
|
|
||||||
- [Current adoption and update guide](https://gitea.maximumdirect.net/eric/policy/src/branch/main/docs/adoption.md).
|
|
||||||
|
|
||||||
Discovery links follow the current upstream project and guide. Adopted-source,
|
|
||||||
notice, and example links remain commit-pinned. Preserve valid pinned example
|
|
||||||
references supplied by the selected policy, or pin them to this review revision
|
|
||||||
after checking the referenced assets; do not turn them into moving-branch links.
|
|
||||||
|
|
||||||
Adapt upstream `agents/AGENTS.md` into Audita's root orientation, retaining its
|
|
||||||
existing applicable instructions. Add the adoption guide's Policy Reading Guide
|
|
||||||
to `docs/development.md` and retain the component-specific task guide. Route
|
|
||||||
policy adoption and updates to `meta.md`, repository recommendations and changes
|
|
||||||
to agents, application-code or structure work to architecture, validation choices
|
|
||||||
to testing, documentation work to documentation, dependency/licensing work to
|
|
||||||
OSS, and release/versioning work to release policy. Read applicable policies
|
|
||||||
before their decisions, reuse unchanged context, and expand reading with scope.
|
|
||||||
Do not require reading the entire suite for every task.
|
|
||||||
|
|
||||||
## Copied Policy Licensing and Local Material
|
|
||||||
|
|
||||||
Follow the selected revision's
|
|
||||||
[canonical policy attribution entry](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/examples/licensing/policy-attribution.md)
|
|
||||||
and [project grant](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/COPYRIGHT).
|
|
||||||
Insert the entry beneath the title in `docs/oss/attribution.md`, substitute the
|
|
||||||
full review commit in all three URLs, and accurately identify copied/adapted
|
|
||||||
material, including orientation or reading-guide text where copied. Preserve
|
|
||||||
supplied creator, copyright, license, disclaimer notices, and modification
|
|
||||||
indications. Keep the material statement concise.
|
|
||||||
|
|
||||||
Copy `CC-BY-SA-4.0.txt` into `docs/oss/licenses/`. Add the adoption guide's scoped
|
|
||||||
notice to `COPYRIGHT`, identifying the adapted policies as CC-BY-SA-4.0 and
|
|
||||||
routing readers to attribution for the affected material. Account accurately for
|
|
||||||
other copied guidance in that scope. Audita's application code remains
|
|
||||||
GPL-3.0-or-later; unrelated documentation is not automatically relicensed.
|
|
||||||
The root GPLv3 `LICENSE` needs no change.
|
|
||||||
|
|
||||||
| Material | Destination or treatment |
|
|
||||||
| --- | --- |
|
|
||||||
| Applicable policies, including `meta.md` and `oss.md` | Copy and customize under `docs/policy/`, retaining provenance. |
|
|
||||||
| Upstream `agents/AGENTS.md` | Adapt into root `AGENTS.md`. |
|
|
||||||
| Selective reading guidance | Integrate into Audita's own `docs/development.md`. |
|
|
||||||
| Upstream README, root AGENTS.md, developer guide, and adoption guide | Keep upstream and consult through links; do not copy as local guides. |
|
|
||||||
| Licensing example collection | Keep upstream with commit-pinned references; use it to create actual notices. Original example formatting is CC0 and alone needs no policy attribution entry. |
|
|
||||||
| Applicable standard and companion license texts | Copy complete, unmodified texts locally; no blanket collection. |
|
|
||||||
|
|
||||||
## Component Attribution
|
|
||||||
|
|
||||||
Complete the previously agreed inventory of relevant incorporated or distributed
|
|
||||||
components: direct and transitive dependencies, copied code/documentation/assets,
|
|
||||||
and applicable Go runtime notices. Determine inclusion from actual material and
|
|
||||||
use, not simply presence in `go.mod`; tooling or consultation alone does not
|
|
||||||
require an entry. Inspect selected sources directly rather than copying a
|
|
||||||
dependency's attribution document wholesale.
|
|
||||||
|
|
||||||
Use the exact House Format in OSS policy 1.8: the prescribed title followed by
|
|
||||||
component entries, factual identification, applicable supplied copyrights and
|
|
||||||
SPDX expressions, standard license/notice labels, and `[Source](COMMIT_URL).`
|
|
||||||
links. Omit unavailable optional fields instead of adding explanatory filler.
|
|
||||||
Preserve required upstream material, license alternatives, and exceptions.
|
|
||||||
Keep research narratives, compliance conclusions, and exception reasoning out
|
|
||||||
of attribution; place reasoning in the OSS policy or an appropriate decision
|
|
||||||
record. This replaces the older roadmap's permission to put exception reasoning
|
|
||||||
in a component entry.
|
|
||||||
|
|
||||||
Upstream component and notice links identify exact imported commits. Local
|
|
||||||
standard texts include directly reachable companions, such as GPLv3 for LGPLv3.
|
|
||||||
Apply the adopted house interpretation of notice links without blanket extra
|
|
||||||
local-copy requirements or repeated approval. Concrete obligations beyond notice
|
|
||||||
reproduction receive their own applicable treatment; the format alone does not
|
|
||||||
certify compatibility or resolve every distribution obligation.
|
|
||||||
|
|
||||||
The reviewed dependency revisions remain useful starting evidence, subject to
|
|
||||||
checking the dependencies selected when implementation occurs:
|
|
||||||
|
|
||||||
| Component | Reviewed version | Source commit |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| ConfigKit | v0.4.1 | `0adb94f59e44827908eff401d4381a398fe17107` |
|
|
||||||
| PromptKit | v0.11.1 | `15e40679546f2e21b6f51cc84b6f0a087a8486db` |
|
|
||||||
| OpenRouter catalog | v2.20260926.0 | `4f731d217e0bc39151c010f50fb5912706e7b6b3` |
|
|
||||||
| Rakestrawhome catalog | v2.20260926.0 | `48918a3ad39fd99843cda94a86133b1b3881d596` |
|
|
||||||
|
|
||||||
These library patches changed licensing/documentation rather than library code
|
|
||||||
or catalog profiles. ConfigKit and PromptKit identify LGPL-3.0-or-later; the
|
|
||||||
selected catalogs identify MIT. Other relevant dependencies still need inventory.
|
|
||||||
|
|
||||||
## Discoverability and Release Integration
|
|
||||||
|
|
||||||
End README with the project-license and attribution statements prescribed by the
|
|
||||||
OSS policy, using Audita's actual grant. `COPYRIGHT` owns the separately licensed
|
|
||||||
policy scope; README links to that grant and the attribution document.
|
|
||||||
|
|
||||||
Update `docs/release.md` to specify the final `COPYRIGHT | LICENSE | ATTRIBUTION`
|
|
||||||
links in every new release note and hosted download page. Use absolute Gitea URLs
|
|
||||||
at the release's immutable tag or source commit. Reuse the checked-in note as the
|
|
||||||
hosted body where supported; verify the publication plugin's supported mechanism
|
|
||||||
and make the necessary focused `.woodpecker/release.yml` change. Avoid separately
|
|
||||||
maintained release prose or notice lists.
|
|
||||||
|
|
||||||
Preserve the current atomic source/tag publication approach, supported targets,
|
|
||||||
and completion point. Align procedure wording with upstream's preparation,
|
|
||||||
ambiguous-push recovery, and asynchronous retry rules. Handle completed-roadmap
|
|
||||||
retirement during preparation, preserving planned/deferred work and repairing
|
|
||||||
links; this roadmap update does not authorize deleting other roadmaps now.
|
|
||||||
|
|
||||||
Check intended future-tag paths against the candidate source without requiring
|
|
||||||
the tag to resolve before publication. Apply the formatting convention
|
|
||||||
prospectively; leave published tags, notes, artifacts, and checksums intact.
|
|
||||||
Concrete historical omissions, if discovered, receive deliberate treatment under
|
|
||||||
their applicable terms and release correction rules.
|
|
||||||
|
|
||||||
## Work Required and Acceptance Criteria
|
|
||||||
|
|
||||||
Implementation covers the seven policy documents, `AGENTS.md`,
|
|
||||||
`docs/development.md`, `COPYRIGHT`, `docs/oss/attribution.md`, applicable files in
|
|
||||||
`docs/oss/licenses/`, README, the release procedure, and focused hosted-body
|
|
||||||
publication wiring. Update this roadmap's implementation status when appropriate.
|
|
||||||
The [implementation plan](implementation.md) owns the editing sequence and
|
|
||||||
verification steps.
|
|
||||||
|
|
||||||
The work is complete when:
|
|
||||||
|
|
||||||
- Shared policy language matches the selected upstream revision after required
|
|
||||||
template removal and customization; retained variations have substantive
|
|
||||||
Audita-specific reasons as identified above.
|
|
||||||
- All seven policies have accurate versions, adoption dates, and file-specific
|
|
||||||
commit-pinned source links. Public discovery links and selective orientation
|
|
||||||
are in place, with no leftover template-only instructions or broken links.
|
|
||||||
- Copied policy/guidance scope, modification status, credit, CC-BY-SA text, and
|
|
||||||
root grant notice follow the documented upstream arrangement while preserving
|
|
||||||
Audita's application license.
|
|
||||||
- Relevant components have verified House Format entries, exact source/notice
|
|
||||||
commits, and complete applicable license texts, without editorial commentary.
|
|
||||||
- README and future release documentation provide the required navigation, and
|
|
||||||
hosted release content comes from the checked-in note through a verified
|
|
||||||
publication mechanism.
|
|
||||||
- Audita's application contracts, architectural decisions, platform commitments,
|
|
||||||
release completion semantics, and published history are preserved.
|
|
||||||
- Affected text, links, license copies, metadata, and publication configuration
|
|
||||||
pass proportionate review and validation. No release is needed solely to test
|
|
||||||
adoption; behavioral workflow changes receive focused validation if required.
|
|
||||||
|
|
||||||
## Scope Boundaries and Open Questions
|
|
||||||
|
|
||||||
No policy decisions remain open for the agreed scope. Component-specific notices
|
|
||||||
and the publication plugin's supported release-body mechanism require factual
|
|
||||||
verification during implementation. Raise a decision only for a concrete
|
|
||||||
unresolved obligation or material tradeoff uncovered by that work.
|
|
||||||
|
|
||||||
This feature does not require application refactoring, dependency upgrades,
|
|
||||||
mandatory scanners, a full SBOM, recurring legal approvals, synchronization
|
|
||||||
checks, additional human release gates, publication-time network checks, or CI
|
|
||||||
waiting. The documentation convention alone does not require downloadable
|
|
||||||
license assets, source bundles, or a different binary package format.
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
# Maintainability Audit: Findings and Recommended Changes
|
|
||||||
|
|
||||||
**Reviewed:** 2026-09-25, source commit `011b021`.
|
|
||||||
**Scope:** Recent partial-decision reconciliation, validation context, prompt
|
|
||||||
alignment, and request-local reference changes, including related existing code.
|
|
||||||
**Status:** R27–R30 addressed; see the resolution record below. Previous findings and resolution records remain in Git
|
|
||||||
history through `011b021`.
|
|
||||||
|
|
||||||
## Architecture Assessment
|
|
||||||
|
|
||||||
The recent changes fit Audita's existing boundaries. Proposal generation,
|
|
||||||
validation, prompt assets, and outcome reporting already have suitable owners;
|
|
||||||
this audit does not recommend a new common package or a broad refactor.
|
|
||||||
|
|
||||||
The strongest opportunities are shared prompt instructions and request-scoped
|
|
||||||
validation context preparation. Two smaller improvements concern recovery-reason
|
|
||||||
classification and partial-result assembly; both predate the latest commits.
|
|
||||||
These are bounded maintenance recommendations, not new release gates or changes
|
|
||||||
to correction policy, response acceptance, or reporting semantics.
|
|
||||||
|
|
||||||
## R27 — P2: Consolidate Shared Prompt Instructions
|
|
||||||
|
|
||||||
**Category:** Repeated behavioral-contract text; risk of future drift.
|
|
||||||
|
|
||||||
**Evidence:** The four [proposal prompts](../../internal/prompts/assets/prompts/modules.grammar.proposal.yaml)
|
|
||||||
repeat literal source-span matching, request-local identity, replacement-text,
|
|
||||||
source-based proposal, and confidence instructions. The three active
|
|
||||||
[validator prompts](../../internal/prompts/assets/prompts/validators.editorial_review.yaml)
|
|
||||||
repeat candidate-reference, supplemental-context, and verdict-confidence guidance.
|
|
||||||
Recent contract changes required coordinated edits across these assets. The
|
|
||||||
[prompt registry](../../internal/prompts/registry.go) already provides a shared
|
|
||||||
hardening fragment supplied through the role-bound adapter.
|
|
||||||
|
|
||||||
**Recommended change:** Extract genuinely common passages into embedded fragments
|
|
||||||
under `internal/prompts/assets/shared`, following the existing fragment-supply
|
|
||||||
approach. Keep the fragments owned by `internal/prompts` and use the existing
|
|
||||||
PromptKit rendering path. Keep module-specific examples, replacement-policy
|
|
||||||
instructions, protected-name exceptions, and semantic criteria in their individual
|
|
||||||
prompt definitions. Do not merge similar-looking rules with different meanings.
|
|
||||||
|
|
||||||
**Acceptance:** Common instructions have one maintained source and appear in all
|
|
||||||
intended rendered prompts. The existing module and validator distinctions remain
|
|
||||||
intact, including glossary replace-all behavior and the spoken-word protected-name
|
|
||||||
exception. Focused rendering checks cover fragment inclusion and representative
|
|
||||||
module-specific guidance without requiring exact-prose snapshots or introducing
|
|
||||||
a separate templating framework.
|
|
||||||
|
|
||||||
## R28 — P2: Prepare Validation Context Once Per Request
|
|
||||||
|
|
||||||
**Category:** Repeated computation and overlapping segment lookup.
|
|
||||||
|
|
||||||
**Evidence:** [BuildLLMValidationRequest](../../internal/framework/validators/llm_validators.go)
|
|
||||||
already indexes transcript segments, but
|
|
||||||
[validationContext](../../internal/framework/validators/llm_context.go) scans the
|
|
||||||
transcript again for each candidate. It also calls
|
|
||||||
[NewProtectedVocabulary](../../internal/framework/validators/protected_terms.go)
|
|
||||||
for each candidate, rebuilding the same vocabulary and compiling its matching
|
|
||||||
expression repeatedly. This is duplicated preparation rather than copied code.
|
|
||||||
|
|
||||||
**Recommended change:** Prepare a segment-position lookup and protected vocabulary
|
|
||||||
once per validation request, then pass that prepared context into the existing
|
|
||||||
context-selection helper. Keep the implementation within `framework/validators`.
|
|
||||||
Continue selecting glossary evidence for each candidate individually because its
|
|
||||||
proposed replacement can mention different terms. A process-wide cache or new
|
|
||||||
context-management package is unnecessary for this cleanup.
|
|
||||||
|
|
||||||
**Acceptance:** Candidates in one validation request reuse the prepared lookup and
|
|
||||||
vocabulary. Context still consists of the available immediate neighbors within the
|
|
||||||
current chunk and relevant glossary entries in glossary order. Candidate-specific
|
|
||||||
replacement text still participates in glossary selection. Existing optional-context
|
|
||||||
trimming, token estimation, and oversized-input handling retain their behavior.
|
|
||||||
Verify these outcomes with focused behavioral tests rather than assertions about
|
|
||||||
private helper call counts or unmeasured performance guarantees.
|
|
||||||
|
|
||||||
## R29 — P3: Share Execution-Recovery Classification
|
|
||||||
|
|
||||||
**Category:** Duplicate reporting rule; predates the latest commits.
|
|
||||||
|
|
||||||
**Evidence:** [Proposal generation](../../internal/framework/proposal_generation/generate.go)
|
|
||||||
and [LLM validation](../../internal/framework/validators/llm_validators.go)
|
|
||||||
independently translate structural-repair and transient-retry counts into
|
|
||||||
`output_repaired` and `service_recovered` reasons. The conversion is the same in
|
|
||||||
both locations; the surrounding success and domain-outcome decisions differ.
|
|
||||||
|
|
||||||
**Recommended change:** Place a small conversion helper in the existing
|
|
||||||
`framework/outcomes` package and use it from both callers. Keep the decision to
|
|
||||||
record recoveries at the call sites, along with domain-specific reasons such as
|
|
||||||
filtered validator decisions. Do not combine proposal and validation orchestration
|
|
||||||
or move domain-success classification into this helper.
|
|
||||||
|
|
||||||
**Acceptance:** Both callers use the same execution-count conversion. Existing
|
|
||||||
reason ordering, successful-operation reporting, and domain-specific additions
|
|
||||||
remain intact. A partially unavailable validation batch continues to record its
|
|
||||||
failure and execution counts without acquiring recovery records merely because a
|
|
||||||
repair or retry occurred. Existing outcome tests should continue to protect these
|
|
||||||
distinctions.
|
|
||||||
|
|
||||||
## R30 — P3: Centralize Partial Validator Result Assembly
|
|
||||||
|
|
||||||
**Category:** Repeated early-return construction; predates the latest commits.
|
|
||||||
|
|
||||||
**Evidence:** Several early returns in
|
|
||||||
[LLMBackedValidator.Validate](../../internal/framework/validators/llm_validators.go)
|
|
||||||
repeat nested slice appends to combine immediate decisions, oversized-input
|
|
||||||
decisions, and decisions from completed model batches, together with accumulated
|
|
||||||
operation outcomes. This makes it easier for a future failure path to omit facts
|
|
||||||
already collected by the validator.
|
|
||||||
|
|
||||||
**Recommended change:** Use a small local result-building helper to assemble the
|
|
||||||
partial result consistently. Keep error handling, batching, success-path ordering,
|
|
||||||
and cardinality enforcement explicit. A general-purpose result-aggregation package
|
|
||||||
is not needed.
|
|
||||||
|
|
||||||
**Acceptance:** Cancellation and failure paths retain the same previously collected
|
|
||||||
decisions and operations. Result ordering and cardinality behavior remain unchanged;
|
|
||||||
the helper does not enforce complete cardinality on interrupted work. Retain the
|
|
||||||
existing partial-progress and fatal-failure coverage, adding tests only for a
|
|
||||||
material gap exposed by the refactor.
|
|
||||||
|
|
||||||
## Boundaries to Preserve
|
|
||||||
|
|
||||||
- Keep proposal and validator reference mapping separate. Proposals can target the
|
|
||||||
same segment repeatedly; duplicate validator decisions make that candidate
|
|
||||||
unavailable. Similar indexing arithmetic alone does not justify a generic
|
|
||||||
reference-management abstraction.
|
|
||||||
- Keep distinct protected-term policy checks separate. They already share the
|
|
||||||
vocabulary implementation and intentionally apply different policies.
|
|
||||||
- Preserve exact source-span matching, request-local references, batching, and
|
|
||||||
partial-success behavior. Consolidation is not a reason to strengthen contracts
|
|
||||||
or broaden the accepted correction scope.
|
|
||||||
|
|
||||||
## Review Verification and Limits
|
|
||||||
|
|
||||||
- This is a source-level maintainability audit, not a finding of demonstrated
|
|
||||||
production failures or a measured performance assessment.
|
|
||||||
- No application tests were rerun for the original read-only audit; its roadmap
|
|
||||||
update used documentation link and whitespace checks. Implementation validation
|
|
||||||
is recorded below.
|
|
||||||
- Future remediation should run focused behavioral tests and the broader suite
|
|
||||||
appropriate to the actual change, following the testing policy. No live-provider
|
|
||||||
evaluation or new evaluation framework is required by these recommendations.
|
|
||||||
- The findings above describe the audited baseline. Implementation and validation
|
|
||||||
are recorded below; no release publication is included.
|
|
||||||
|
|
||||||
## Resolution Record
|
|
||||||
|
|
||||||
Implemented as focused changes within the existing packages. Proposal and validator
|
|
||||||
reference mapping and distinct protected-term policies remain separate.
|
|
||||||
|
|
||||||
| Finding | Resolution and evidence |
|
|
||||||
| --- | --- |
|
|
||||||
| R27 | Common proposal and validation instructions live in two embedded shared fragments, supplied through the existing role-bound PromptKit adapter. Individual YAML definitions retain policy differences and examples. Rendering tests check fragment inclusion once per applicable prompt and representative module-specific guidance. |
|
|
||||||
| R28 | Validation request construction builds one segment-position lookup and protected vocabulary, reused by context selection for every candidate. A new test verifies that two replacements of the same source span receive their own relevant glossary entries. Existing neighbor, glossary-order, chunk-boundary, and budget tests pass. |
|
|
||||||
| R29 | Both proposal generation and validation use `outcomes.ExecutionRecoveries`. Call sites still control success and domain-specific recovery reasons. Existing tests cover combined recoveries and failed validation with repair counts but no recovery record. |
|
|
||||||
| R30 | A local result builder combines collected decisions and operation outcomes for partial returns and completed validation. Sorting and full cardinality checks remain outside it. Existing fatal-batch, partial-progress, runner, and reporting tests pass. |
|
|
||||||
|
|
||||||
Validation: `go test ./...` passed, including PromptKit preparation, partial-decision
|
|
||||||
reconciliation, subprocess reports, and fatal-progress coverage. Documentation
|
|
||||||
links and whitespace checks passed. No live model evaluation or performance
|
|
||||||
measurement was performed; the changes make no new model-quality or latency
|
|
||||||
promise.
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
# Resilient Processing and Focused Run Reports
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-24
|
|
||||||
|
|
||||||
**Status:** Implemented. This document records the selected behavior and user
|
|
||||||
intent. Concrete design decisions and completion evidence are in
|
|
||||||
[historical implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/a83a518/docs/roadmap/implementation.md).
|
|
||||||
|
|
||||||
## Purpose and User Intent
|
|
||||||
|
|
||||||
Audita should reliably produce a useful transcript even when its LLM services
|
|
||||||
cannot improve it. Exhausted retries or unusable model responses must not turn
|
|
||||||
an otherwise processable transcript into a failed production run.
|
|
||||||
|
|
||||||
> Failure of every LLM operation across every stage and chunk still produces a
|
|
||||||
> successful output containing the deterministically normalized transcript, with
|
|
||||||
> no LLM corrections applied. Partial success preserves as many fully validated,
|
|
||||||
> applicable corrections as possible.
|
|
||||||
|
|
||||||
Deterministic normalization remains intentional and may change segment layout,
|
|
||||||
identifiers, and text before LLM work begins. Output uses the selected transcript
|
|
||||||
encoder; byte-for-byte preservation of the original input and mapping corrections
|
|
||||||
back to pre-normalization segmentation are not requirements.
|
|
||||||
|
|
||||||
The overriding goal is to minimize how much useful processing is lost because of
|
|
||||||
one problematic transcript segment. A stage/chunk work unit is an attribution
|
|
||||||
boundary, not an all-or-nothing transaction.
|
|
||||||
|
|
||||||
Existing reports contain useful information, but there are no compatibility
|
|
||||||
obligations for reporting or debug output. Reuse existing machinery only when it
|
|
||||||
makes the implementation simpler and clearer; preservation is not itself a goal.
|
|
||||||
Internal structures and measurements must serve Audita's processing workflow.
|
|
||||||
The reporting package adapts those outcomes to structured and human-facing
|
|
||||||
presentations, rather than reshaping processing around a reporting convenience.
|
|
||||||
|
|
||||||
## Historical Baseline and Implemented Change
|
|
||||||
|
|
||||||
The [pipeline](../internal/pipeline.md), [LLM integration](../integrations/openai-compatible-llm.md),
|
|
||||||
[report assembly](../internal/diagnostics-reporting.md), and
|
|
||||||
[artifact lifecycle](../operations.md) describe current behavior.
|
|
||||||
|
|
||||||
Before this work, exhausted structural repairs could become warnings and skipped
|
|
||||||
proposals or rejected validation batches. Other model runtime failures canceled
|
|
||||||
peer work, stopped subsequent modules, and prevented final transcript output.
|
|
||||||
Successful repairs appeared in execution observations rather than a dedicated
|
|
||||||
recovery classification. Detailed artifacts were written during ordinary runs
|
|
||||||
and could later be removed by retention. The working transcript was re-chunked
|
|
||||||
between modules.
|
|
||||||
|
|
||||||
This feature changed those behaviors. It builds on the completed
|
|
||||||
[library adoption](library-adoption.md) and [review remediation](remediation.md).
|
|
||||||
Fatal cancellation remains necessary; unsuccessful model work must be separated
|
|
||||||
from failures that actually require cancellation of the run.
|
|
||||||
|
|
||||||
## Fixed Chunks and Ordered Stages
|
|
||||||
|
|
||||||
Normalize first, then partition the transcript once. Preserve chunk boundaries,
|
|
||||||
segment membership, segment identity, and ordering throughout LLM processing.
|
|
||||||
Text evolves as accepted corrections are applied. Recombine chunks in original
|
|
||||||
order after the LLM stages finish.
|
|
||||||
|
|
||||||
A stage is a configured module instance. Repeated instances of the same module
|
|
||||||
have distinct identities. Proposal generation and each validator are operations
|
|
||||||
within a stage; validation may use multiple batches within a chunk. A work unit
|
|
||||||
is one stage instance applied to one chunk.
|
|
||||||
|
|
||||||
Preserve the overall stage sequence: finish a stage across its chunks before
|
|
||||||
advancing to the next stage. Concurrent chunk work shares PromptKit's backend
|
|
||||||
capacity. Failure in one unit does not prevent other chunks or later stages
|
|
||||||
from executing.
|
|
||||||
|
|
||||||
Retain operator control over chunk size and the current defaults. Production
|
|
||||||
experience indicates that chunk size affects model latency nonlinearly and that
|
|
||||||
useful settings are well below backend context limits. Tuning is an empirical
|
|
||||||
operator decision. Do not add automatic headroom calculations, cross-profile
|
|
||||||
sizing, adaptive repartitioning, or automatic tuning in this feature. A later
|
|
||||||
operation that cannot process its input follows the same safe error handling;
|
|
||||||
it does not silently alter chunk boundaries. Validation batching within a chunk
|
|
||||||
remains supported.
|
|
||||||
|
|
||||||
Independent whole-pipeline traversal by chunks and domain-aware response repair
|
|
||||||
are explicitly deferred in [future work](future.md). Read-only neighboring
|
|
||||||
context, cross-chunk edits, and transcript-wide restructuring are outside this
|
|
||||||
unit, without becoming permanent prohibitions on future capabilities.
|
|
||||||
|
|
||||||
## Partial Success and Failure Isolation
|
|
||||||
|
|
||||||
- Keep bounded transport retries, structural repairs, operation deadlines, and
|
|
||||||
shared backend capacity. Completion comes from safe fallback, not unlimited
|
|
||||||
attempts or an outer retry loop around PromptKit.
|
|
||||||
- Apply a correction only after it passes every required validator and the
|
|
||||||
application checks. Failed validation must never be treated as approval.
|
|
||||||
- If a validation batch fails, exclude its affected candidates while allowing
|
|
||||||
other candidates to continue through the remaining validators. Preserve fully
|
|
||||||
validated, applicable corrections from successful batches in the same unit.
|
|
||||||
- Track candidates by stable proposal identity. Batch membership can change
|
|
||||||
between validators; equal batch numbers do not establish candidate identity.
|
|
||||||
- A failed proposal-generation operation leaves its chunk unchanged for that
|
|
||||||
stage because there are no reliable proposals to salvage. Failures later in
|
|
||||||
validation need not discard unrelated successful candidates.
|
|
||||||
- Preserve earlier stages' corrections and other chunks' successful work.
|
|
||||||
Record partial success explicitly; a unit can apply corrections and have errors.
|
|
||||||
- Continue despite model-operation failures, including exhausted repairs/retries,
|
|
||||||
unusable model decisions, provider/transport failures, local operation deadlines,
|
|
||||||
and admission failures. An operation need not exhaust its attempt budget to fail.
|
|
||||||
- A valid empty proposal list or valid assessments rejecting all corrections is
|
|
||||||
successful processing. Correction count alone does not determine failure.
|
|
||||||
- Keep safe filtering of defective proposals. Do not retry ordinary editorial
|
|
||||||
rejections. For now, an unusable validator decision set excludes the affected
|
|
||||||
candidates and records an error after existing permitted recovery. New bounded
|
|
||||||
feedback/regeneration for domain-invalid responses belongs to future work.
|
|
||||||
- Retain fatal treatment for invalid input/configuration, internal defects,
|
|
||||||
explicit run cancellation, and inability to produce/write the transcript.
|
|
||||||
Caller cancellation is distinct from a local model-operation timeout.
|
|
||||||
- Completed runs with model errors return success. Required report-write failures
|
|
||||||
are an additional fatal output condition when reporting is enabled, as below.
|
|
||||||
|
|
||||||
PromptKit owns transport, transient retries, structural repair, and admission.
|
|
||||||
Audita owns domain acceptance, partial outcomes, fallback, and classification.
|
|
||||||
ConfigKit owns configuration resolution. Debug and reporting choices must not
|
|
||||||
change correction decisions, retry budgets, or continuation.
|
|
||||||
|
|
||||||
## Errors, Recoveries, and Debug
|
|
||||||
|
|
||||||
Classify by eventual outcome and impact. These are three reporting categories;
|
|
||||||
debug is evidence, not a failure severity. Whether execution continues is a
|
|
||||||
separate property.
|
|
||||||
|
|
||||||
| Category | Meaning | Representative cases |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Errors | Intended work could not complete reliably, or a fatal run-level operation failed. Affected candidates are excluded; other corrections can succeed. | Exhausted repair; unavailable provider; unusable validation decisions; operation deadline. |
|
|
||||||
| Recoveries | A problem was handled successfully through retries, repair, or safe filtering. No operator intervention is implied. | Invalid output repaired; transient service failure recovered; defective proposals safely discarded. |
|
|
||||||
| Debug | Detailed evidence of processing and ordinary decisions. | Proposals, applied corrections, validator decisions, prompts/responses, individual attempts, execution traces. |
|
|
||||||
|
|
||||||
Normal editorial disagreement belongs in debug. A validator correctly rejecting
|
|
||||||
a meaning-changing edit is expected behavior. A proposal referring to nonexistent
|
|
||||||
original text is defective; safely filtering it is a recovery. An unresolved
|
|
||||||
missing/duplicate decision-index response is an error, not a recovery.
|
|
||||||
|
|
||||||
Record an operation's eventual outcome once. Several failed attempts followed by
|
|
||||||
success produce a consolidated recovery. Attempts ending in failure contribute
|
|
||||||
to error details, not additional recoveries. Earlier operations that recovered
|
|
||||||
remain recorded if a later operation introduces an error in the same unit.
|
|
||||||
|
|
||||||
Separate output-quality recovery, defective-proposal filtering, and service
|
|
||||||
recovery with structured reasons. Network instability must not look like a prompt
|
|
||||||
defect. Do not introduce an arbitrary warning threshold or a fourth category.
|
|
||||||
|
|
||||||
### Counting and Processing Volume
|
|
||||||
|
|
||||||
- Count error-bearing work units once per stage/chunk, consolidating underlying
|
|
||||||
operation failures as details. A partially successful unit still counts once.
|
|
||||||
- Count recoveries once per recovered operation. Aggregate defective-proposal
|
|
||||||
filtering per proposal response, retaining affected-proposal counts as detail.
|
|
||||||
Attempts and individual discarded proposals are not independent incidents.
|
|
||||||
- Keep work-unit coverage separate from operation/recovery counts. Show processing
|
|
||||||
volume with explicitly labeled denominators; do not divide recovered-operation
|
|
||||||
counts by work-unit counts and call that a recovery rate.
|
|
||||||
- Report fatal run-level errors separately from the stage/chunk grid when they
|
|
||||||
cannot be attributed to a work unit. Do not invent a chunk to accommodate them.
|
|
||||||
- Derive YAML records, Markdown summaries, and CLI counts from actual processing
|
|
||||||
outcomes with deterministic ordering, rather than independently calculating them.
|
|
||||||
|
|
||||||
## Production Outputs, Placement, and Lifecycle
|
|
||||||
|
|
||||||
Reporting is enabled by default. The ordinary bundle is:
|
|
||||||
|
|
||||||
- `errors.yml`: structured error-bearing units, their underlying failures, and
|
|
||||||
applicable run-level failures.
|
|
||||||
- `recoveries.yml`: structured recovered operations and pertinent counts.
|
|
||||||
- `report.md`: a concise human-facing summary of outcome, corrections, processing
|
|
||||||
volume, errors, recoveries, and their locations in the pipeline.
|
|
||||||
|
|
||||||
Write all three documents, including empty event lists for uneventful completed
|
|
||||||
runs. Use Audita-owned schemas with safe explanations and stable reason codes;
|
|
||||||
retain run, stage-instance, chunk, operation, and validator/batch identities where
|
|
||||||
applicable. The implementation plan specifies the versioned schemas; neither old
|
|
||||||
report formats nor raw library structures define the new output contract.
|
|
||||||
|
|
||||||
Place reports beside the output transcript. Retain transcript-to-stdout support
|
|
||||||
with an explicit report-directory selection, defaulting to the invocation working
|
|
||||||
directory. Preserve the existing overwrite convention: a new run can replace files
|
|
||||||
at its selected destinations. Do not add a mandatory fresh-directory restriction
|
|
||||||
or compatibility exports.
|
|
||||||
|
|
||||||
Before model work, create initial versions of all three reports carrying a common
|
|
||||||
run identifier and explicit `in_progress` status (rendered as prose in Markdown).
|
|
||||||
Fail immediately if initialization fails. Also check the transcript destination;
|
|
||||||
creating neighboring reports does not prove that an existing transcript file is
|
|
||||||
writable. Preflight must not prematurely destroy an existing transcript.
|
|
||||||
|
|
||||||
Finalize all required outputs and check every final write. Initial write success
|
|
||||||
cannot guarantee later success if space or permissions change. Failure to write
|
|
||||||
the transcript or finalize a required report returns nonzero and must not erase
|
|
||||||
an already produced transcript. Where possible, finalize available reports with
|
|
||||||
an explicit failed status on fatal termination. Abrupt termination may leave
|
|
||||||
`in_progress`; elaborate crash-recovery machinery is not required.
|
|
||||||
|
|
||||||
Provide an option to disable reporting entirely. It suppresses all three files
|
|
||||||
(`errors.yml`, `recoveries.yml`, and `report.md`), including initialization and
|
|
||||||
final writes, while retaining the brief CLI summary and independently selectable
|
|
||||||
debug output. Disabled reporting must not create or require a writable report
|
|
||||||
destination, and must not remove reports from earlier runs.
|
|
||||||
Transcript output failures remain fatal regardless of reporting selection.
|
|
||||||
|
|
||||||
Ordinary reports survive independently of debug cleanup. Reporting configuration
|
|
||||||
and finalization must not depend on retaining a debug run directory.
|
|
||||||
|
|
||||||
## Debug and Internal Boundaries
|
|
||||||
|
|
||||||
Production issue records are compact. Full responses, transcript excerpts,
|
|
||||||
correction lists, and complete validation decisions are opt-in debug evidence.
|
|
||||||
Use a single ConfigKit-backed debug boolean with a `--debug` CLI override. Debug
|
|
||||||
artifacts may use JSON where useful. Preserve configured-secret redaction in every
|
|
||||||
presentation.
|
|
||||||
|
|
||||||
Debug-off prevents detailed artifact persistence; writing and later deleting
|
|
||||||
those files is insufficient. In-memory domain state needed to process candidates
|
|
||||||
correctly is not debug output. Debug and ordinary reporting are independent
|
|
||||||
selections and do not alter processing behavior.
|
|
||||||
|
|
||||||
There is no obligation to retain `--report-json`, old artifact layouts, parallel
|
|
||||||
exports, or old retention settings. Reuse code only where useful to the new
|
|
||||||
workflow. Enabled debug output is retained on success or failure; disabled debug
|
|
||||||
creates no debug directory. This supersedes the retention question in the
|
|
||||||
[runtime follow-up](runtime-behavior.md). The legacy `never` behavior is not the
|
|
||||||
meaning of the new reporting-disable option. Optional debug-write failures produce
|
|
||||||
a safe stderr diagnostic without changing correction decisions or the run exit
|
|
||||||
status; ordinary report and transcript-write failures retain their fatal semantics.
|
|
||||||
|
|
||||||
## Deterministic Human-Facing Presentation
|
|
||||||
|
|
||||||
Use the CLI style:
|
|
||||||
|
|
||||||
> Completed: 18 corrections applied; 3 work units with errors; 7 recoveries.
|
|
||||||
|
|
||||||
Do not say that all error-bearing units were skipped: some apply valid corrections.
|
|
||||||
Include report locations when reporting is enabled and the files exist. Send the
|
|
||||||
summary to stderr so stdout remains exclusively available for transcript output.
|
|
||||||
Disabled reporting does not suppress the summary or fatal-error messages.
|
|
||||||
|
|
||||||
Keep `report.md` concise and high-level. Use separate error and recovery matrices
|
|
||||||
with stages as columns and chunks as rows. Include every chunk in fixed order;
|
|
||||||
retain configured stage order and distinct labels for repeated module instances.
|
|
||||||
No automatic width detection, thresholds, adaptive omission, or table splitting.
|
|
||||||
A wide or long table is acceptable.
|
|
||||||
|
|
||||||
The error matrix counts error-bearing work units: each attempted cell is 0 or 1.
|
|
||||||
Recovery cells count recovered operations and can exceed 1. An error cell may
|
|
||||||
coexist with recoveries and successfully applied corrections in the same unit.
|
|
||||||
Distinguish completed work with zero events from unattempted work; do not portray
|
|
||||||
canceled/unavailable processing as clean completion. Include totals, processing
|
|
||||||
volume, and a short reason breakdown. Detailed inspection belongs in YAML or debug.
|
|
||||||
|
|
||||||
## Target Acceptance Criteria
|
|
||||||
|
|
||||||
- Total LLM failure still returns success and the encoded normalized transcript,
|
|
||||||
provided required output writes succeed.
|
|
||||||
- Mixed-success runs preserve independently validated, applicable corrections
|
|
||||||
within the same stage/chunk as a failed batch. No candidate bypasses a required
|
|
||||||
validator; later validators handle survivors by stable proposal identity.
|
|
||||||
- Subsequent stages and unaffected chunks execute after recoverable failures.
|
|
||||||
Fatal errors and explicit cancellation stop and join active work appropriately.
|
|
||||||
- Chunk boundaries and identity remain fixed, with operator-selected sizing and
|
|
||||||
unchanged defaults; final recombination preserves ordering.
|
|
||||||
- Valid no-change/editorial-rejection outcomes are successful. Repaired/filtered
|
|
||||||
output and unresolved failures receive distinct classifications and counts.
|
|
||||||
- YAML, Markdown, and CLI agree on partial outcomes and totals; error matrices
|
|
||||||
count affected work units, recovery matrices count recovered operations.
|
|
||||||
- With reporting enabled, all three reports are initialized before model work,
|
|
||||||
finalized and retained, including empty lists. Initial or final required-output
|
|
||||||
write failures produce nonzero exits without destroying completed transcript output.
|
|
||||||
- Reporting can be disabled without requiring its destination. Debug can be enabled
|
|
||||||
independently; neither selection changes processing outcomes. Debug-off writes
|
|
||||||
no detailed debug artifacts, and stdout transcript output remains supported.
|
|
||||||
- Reports use the fixed deterministic layout without adaptive presentation logic.
|
|
||||||
- Meaningful controlled-provider tests cover exhausted recovery, failed validation
|
|
||||||
batches with successful survivors, total failure, fatal cancellation, output
|
|
||||||
failures, and reporting/debug selections. Preserve domain safeguards, capacity
|
|
||||||
limits, and redaction.
|
|
||||||
- Canonical CLI, configuration, operations, integration, internal, and architecture
|
|
||||||
documentation describes the implemented final behavior.
|
|
||||||
|
|
||||||
## Implementation Reference
|
|
||||||
|
|
||||||
The product decisions are settled. The [historical implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/a83a518/docs/roadmap/implementation.md) defines
|
|
||||||
the selected settings and precedence, YAML schemas, counts and ordering, interrupted
|
|
||||||
work presentation, destination checks, and output finalization. It also owns the
|
|
||||||
ordered implementation stages and validation work. This document remains the
|
|
||||||
reference for the desired behavior and its acceptance criteria.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Runtime Behavior Decisions
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-24
|
|
||||||
|
|
||||||
The documentation migration identified existing behaviors for separate product
|
|
||||||
consideration. The resilience implementation resolved the diagnostics-retention
|
|
||||||
question; the input-timestamp question remains open.
|
|
||||||
|
|
||||||
- Resolved: legacy `auto`/`always`/`never` retention settings were removed.
|
|
||||||
Debug is explicitly enabled with `diagnostics.debug` or `--debug`, creates no
|
|
||||||
directory when disabled, and retains its directory on every outcome when enabled.
|
|
||||||
- Resolved: [configuration](../config.md) now treats explicit zero as disabling
|
|
||||||
transient retries or structural repair independently; PromptKit performs both.
|
|
||||||
- Decide whether missing or null input timestamps should be rejected; current
|
|
||||||
decoding is documented in the [input contract](../integrations/transcript-glossary-files.md).
|
|
||||||
|
|
||||||
These decisions are independent of the completed documentation migration and
|
|
||||||
release workflow.
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
# Consistent Span Deletion
|
|
||||||
|
|
||||||
**Last updated:** 2026-09-25
|
|
||||||
|
|
||||||
**Status:** Implemented. This document owns the desired behavior and user intent.
|
|
||||||
The [historical implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/60e2fe7/docs/roadmap/implementation.md)
|
|
||||||
records the concrete steps, selected implementation details, and completion
|
|
||||||
evidence.
|
|
||||||
|
|
||||||
## Purpose and User Intent
|
|
||||||
|
|
||||||
Align proposal prompts, the LLM response schema, internal validation, and
|
|
||||||
application behavior so legitimate text-span deletions can be considered and
|
|
||||||
applied through the ordinary correction workflow. An empty replacement should
|
|
||||||
not trigger structural repair merely because it is empty, nor invalidate other
|
|
||||||
usable corrections returned alongside it.
|
|
||||||
|
|
||||||
This is a consistency fix within Audita's current editing scope. It does not
|
|
||||||
authorize whole-segment removal, broaden module responsibilities, or introduce
|
|
||||||
new recovery machinery. The accepted [resilience behavior](resilience.md) remains
|
|
||||||
in effect, including partial success, fixed chunks, and validation before mutation.
|
|
||||||
|
|
||||||
## Historical Baseline (`0b6cd98`)
|
|
||||||
|
|
||||||
The source reviewed at `0b6cd98` contains a mismatch:
|
|
||||||
|
|
||||||
- The [spoken-word prompt](../../internal/prompts/assets/prompts/modules.spoken_word.proposal.yaml)
|
|
||||||
permits removing fillers and asks for the exact affected span and replacement,
|
|
||||||
without explaining how to represent deletion. Grammar permits punctuation and
|
|
||||||
spacing changes, which can also naturally involve deletion.
|
|
||||||
- The shared [correction response schema](../../internal/framework/responseschema/assets/correction_set.v1.json)
|
|
||||||
requires `corrected_text` to have at least one character. Every built-in proposal
|
|
||||||
module uses that schema. An empty replacement invalidates the response before
|
|
||||||
Audita evaluates individual proposals.
|
|
||||||
- PromptKit performs bounded structural repair. If repair does not produce a
|
|
||||||
valid response, the proposal operation fails and yields no candidates, including
|
|
||||||
otherwise valid proposals in the same response. The current resilience path
|
|
||||||
records the error and continues other work.
|
|
||||||
- [CorrectionProposal](../../internal/framework/proposals/proposal.go) already
|
|
||||||
allows empty replacement text. The
|
|
||||||
[preview/application logic](../../internal/framework/proposals/preview.go)
|
|
||||||
supports literal span deletion when the resulting segment remains nonblank.
|
|
||||||
- Domain shape checks currently reject whitespace-only original spans, although
|
|
||||||
the response schema permits them and grammar prompts permit spacing cleanup.
|
|
||||||
This is another restriction to align with the target contract below.
|
|
||||||
- The validator named `non_empty_corrected_text` actually checks the resulting
|
|
||||||
segment, not the replacement string. Both this check and application prevent
|
|
||||||
a segment from becoming empty or whitespace-only. Application changes segment
|
|
||||||
text; it does not remove segment records.
|
|
||||||
|
|
||||||
A temporary controlled-provider review reproduced whole-response rejection for
|
|
||||||
an empty replacement alongside a valid correction, and immediate schema acceptance
|
|
||||||
for a space-only replacement. Existing proposal tests confirm internal support for
|
|
||||||
partial deletion and rejection of blank resulting segments. These observations
|
|
||||||
explain a plausible production failure mechanism; they do not establish the cause
|
|
||||||
of any particular historical incident without its payload and running version.
|
|
||||||
|
|
||||||
## Target Correction Contract
|
|
||||||
|
|
||||||
- `original_text` identifies an existing, nonempty literal span in the current
|
|
||||||
target segment. Whitespace-only spans are eligible; an empty string identifies
|
|
||||||
no source span and remains invalid. Matching and ambiguity checks still apply
|
|
||||||
under the module's replacement policy: `require_unique` for grammar, spoken-word,
|
|
||||||
and homophones; `replace_all` for glossary.
|
|
||||||
- `corrected_text` remains a required string. An empty string means delete the
|
|
||||||
identified span; it does not mean remove the segment record or skip validation.
|
|
||||||
Missing fields and non-string values remain structurally invalid.
|
|
||||||
- After applying a correction, the segment must contain non-whitespace text.
|
|
||||||
Enforce this using the source segment and replacement result in Audita, not a
|
|
||||||
minimum replacement length in the structural schema.
|
|
||||||
- Every accepted deletion passes the same required validators as other edits in
|
|
||||||
its module, including confidence, protected terms, editorial review, and meaning
|
|
||||||
preservation where applicable. Empty replacement is neither automatic approval
|
|
||||||
nor an automatic rejection.
|
|
||||||
- Application preserves segment identity, speaker, timing, membership, ordering,
|
|
||||||
and fixed chunk boundaries. Recheck applicability against text produced by
|
|
||||||
earlier accepted corrections, retaining existing conflict handling.
|
|
||||||
- Preserve literal replacement behavior. Prompts should select spans and
|
|
||||||
replacements that handle adjacent whitespace and punctuation appropriately;
|
|
||||||
this feature does not add automatic trimming or a second normalization pass.
|
|
||||||
Neither side of a correction is rejected solely for containing only whitespace.
|
|
||||||
For example, reducing a uniquely identified run of two spaces to one space may
|
|
||||||
be appropriate; validity still depends on the resulting text and domain checks.
|
|
||||||
|
|
||||||
Preserve the intended safeguards and outcomes, not every detail of the current
|
|
||||||
implementation. Validator ordering, internal structure, and payload construction
|
|
||||||
may change when needed to achieve this consistent contract. Existing behavior or
|
|
||||||
a passing test does not justify retaining a conflicting restriction.
|
|
||||||
|
|
||||||
Illustrative cases:
|
|
||||||
|
|
||||||
| Segment | Original span | Replacement | Intended handling |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `uh hello` | `uh ` | empty string | Structurally valid; may produce `hello` after required validation. |
|
|
||||||
| `uh hello` | `uh hello` | `hello` | Remains a valid representation of the same cleanup, subject to validation. |
|
|
||||||
| `hello world` | two spaces | one space | Eligible spacing correction, subject to unique matching and required validation. |
|
|
||||||
| `uh` | `uh` | empty string | Structurally valid proposal, then filtered because the resulting segment would be blank. |
|
|
||||||
| `hello world` | `hello world` | spaces only | Filtered because the resulting segment would be blank. |
|
|
||||||
|
|
||||||
These examples describe eligibility and mechanical results, not unconditional
|
|
||||||
editorial approval. An unchanged segment or valid empty correction list remains
|
|
||||||
a successful no-change outcome.
|
|
||||||
|
|
||||||
## Prompt and Module Alignment
|
|
||||||
|
|
||||||
Review relevant prompt definitions for consistency and edit only those needing
|
|
||||||
clarification. Where needed, explain empty-string deletion, whitespace-only spans,
|
|
||||||
and the resulting-segment limit with a concise, module-appropriate example. The
|
|
||||||
model should omit a correction that would leave a blank segment rather than invent
|
|
||||||
padding or replacement content. Consistent guidance is the goal; adding deletion
|
|
||||||
language to every prompt is not a requirement.
|
|
||||||
|
|
||||||
Retain each module's existing scope:
|
|
||||||
|
|
||||||
- Spoken-word cleanup can propose appropriate filler or dysfluency deletion while
|
|
||||||
preserving substantive content and intentional emphasis.
|
|
||||||
- Grammar can propose permitted punctuation or spacing deletion; allowing an
|
|
||||||
empty replacement does not authorize word removal or spoken-word cleanup.
|
|
||||||
- Glossary and homophone modules retain their existing correction purposes.
|
|
||||||
Shared deletion syntax does not grant them new editorial responsibilities.
|
|
||||||
|
|
||||||
Review the active LLM-validator prompts and their input construction as well as
|
|
||||||
proposal prompts. Validators must understand the before/after effect of deletion
|
|
||||||
and assess it under their existing criteria. A correct negative editorial decision
|
|
||||||
remains ordinary processing, not a defect that should be regenerated until approved.
|
|
||||||
|
|
||||||
The resulting-segment validator's name and documented identity should describe
|
|
||||||
that responsibility accurately, rather than imply that the replacement string
|
|
||||||
itself must be nonempty. Preserve the existing blank-result failure reason and
|
|
||||||
safeguard while making that deliberate identity change; current chain position
|
|
||||||
and preview implementation are not independent product requirements.
|
|
||||||
|
|
||||||
## Outcomes and Reporting
|
|
||||||
|
|
||||||
A legitimate deletion is an ordinary correction. Empty replacement alone must
|
|
||||||
produce neither structural repair nor a recovery classification. If another
|
|
||||||
problem required successful repair or filtering, retain that actual recovery.
|
|
||||||
|
|
||||||
A schema-valid proposal that would blank its stage-input segment is a defective
|
|
||||||
proposal handled through existing safe filtering. Preserve the
|
|
||||||
`empty_resulting_segment` reason
|
|
||||||
and consolidated `proposals_filtered` recovery behavior; other usable proposals
|
|
||||||
in the response continue through required validation. Ordinary editorial rejection
|
|
||||||
remains debug evidence rather than a model error or filtering recovery.
|
|
||||||
|
|
||||||
A stage can propose several edits to one segment in the same response. Each edit
|
|
||||||
is evaluated against the stage input, then approved edits are applied sequentially.
|
|
||||||
If a proposal was applicable to that input but an earlier accepted edit makes it
|
|
||||||
unsafe, skip it as an apply-time conflict. Do not count that conflict as a defective
|
|
||||||
proposal, model error, or filtering recovery. For example, in `"uh um"`, deleting
|
|
||||||
`"uh "` and deleting `"um"` each leaves text individually; after the first deletion,
|
|
||||||
the second must be skipped to prevent a blank result. Retain the skipped decision
|
|
||||||
in debug evidence. The existing runner already distinguishes this situation.
|
|
||||||
|
|
||||||
Debug evidence must accurately retain the empty replacement and the corresponding
|
|
||||||
validation/application outcome. Ordinary reports retain their existing counts and
|
|
||||||
purpose; no new report schema, incident category, or deletion-specific counter is
|
|
||||||
needed. Unrelated structural defects may still exhaust PromptKit repair and produce
|
|
||||||
an error for the operation, as today.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- A controlled valid response containing an empty replacement reaches Audita's
|
|
||||||
domain validation without structural repair solely due to that empty string.
|
|
||||||
- A permitted span deletion can pass the required validators and reach the final
|
|
||||||
transcript while preserving segment metadata and fixed chunk membership.
|
|
||||||
- A mixed response can apply an approved deletion and another approved correction;
|
|
||||||
an empty replacement does not invalidate the whole response.
|
|
||||||
- A proposal that would blank its stage-input segment is filtered independently,
|
|
||||||
retains the existing reason, and does not prevent unrelated valid candidates
|
|
||||||
from succeeding. A proposal made unsafe only by an earlier accepted edit is
|
|
||||||
skipped without adding an error or filtering recovery; a blank result is still
|
|
||||||
prevented and the conflict remains visible in debug evidence.
|
|
||||||
- A uniquely matching whitespace-only original span can reach validation and
|
|
||||||
application. Empty originals remain invalid, ambiguous matches remain subject
|
|
||||||
to module policy, and whitespace-only resulting segments remain disallowed.
|
|
||||||
- Protected-term and meaning-changing deletions remain subject to their normal
|
|
||||||
rejection rules. No candidate bypasses required validation.
|
|
||||||
- A deletion needing no recovery counts as an ordinary applied correction; actual
|
|
||||||
repair/filtering and editorial rejection retain their existing classifications.
|
|
||||||
Debug evidence represents the empty replacement faithfully.
|
|
||||||
- Schema and domain tests distinguish an empty string from missing/non-string
|
|
||||||
replacement values and distinguish empty replacement from blank resulting text.
|
|
||||||
- Prompt guidance and validator naming express the same contract as the code.
|
|
||||||
Tests protect outcomes rather than exact prompt prose or incidental formatting.
|
|
||||||
|
|
||||||
Use deterministic, offline controlled responses for behavioral integration tests;
|
|
||||||
no live-provider or paid evaluation is a prerequisite. Representative production
|
|
||||||
examples can inform prompt quality without expanding this into an evaluation
|
|
||||||
framework or guaranteeing that models always follow the instructions.
|
|
||||||
|
|
||||||
## Deferred Work
|
|
||||||
|
|
||||||
[Whole-segment removal](future.md#whole-segment-removal) is only potential future
|
|
||||||
work and first requires deciding whether its benefit warrants implementation.
|
|
||||||
Domain-aware regeneration and independent whole-pipeline traversal also remain
|
|
||||||
in [future.md](future.md). This feature changes neither retry budgets nor the
|
|
||||||
Audita/PromptKit ownership boundary and requires no upstream library feature.
|
|
||||||
|
|
||||||
Implementation sequencing and concrete names belong to the
|
|
||||||
[historical implementation plan](https://gitea.maximumdirect.net/eric/audita/src/commit/60e2fe7/docs/roadmap/implementation.md).
|
|
||||||
The target remains consistent deletion of spans within surviving segments, with
|
|
||||||
no new operator controls or report types.
|
|
||||||
15
examples/configkit-inheritance.yml
Normal file
15
examples/configkit-inheritance.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: 2
|
||||||
|
profiles_dir: profiles/local
|
||||||
|
backends:
|
||||||
|
local-base:
|
||||||
|
endpoint: http://127.0.0.1:11434/v1
|
||||||
|
concurrency: 4
|
||||||
|
queue_capacity: 0
|
||||||
|
local:
|
||||||
|
extends: local-base
|
||||||
|
concurrency: 2
|
||||||
|
llm:
|
||||||
|
proposal_profile: local-proposal
|
||||||
|
validation_profile: local-validation
|
||||||
|
pipeline:
|
||||||
|
modules: [grammar]
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
profiles_dir: profiles/local
|
||||||
backends:
|
backends:
|
||||||
local:
|
local:
|
||||||
endpoint: http://127.0.0.1:11434/v1
|
endpoint: http://127.0.0.1:11434/v1
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
profiles_dir: profiles/service
|
||||||
backends:
|
backends:
|
||||||
service:
|
service:
|
||||||
endpoint: https://openrouter.ai/api/v1
|
endpoint: https://openrouter.ai/api/v1
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
id: local-proposal
|
|
||||||
backend: local
|
|
||||||
model: replace-with-local-model
|
|
||||||
temperature: 0
|
|
||||||
max_tokens: 2048
|
|
||||||
top_p: 1
|
|
||||||
timeout_seconds: 120
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
id: local-validation
|
|
||||||
backend: local
|
|
||||||
model: replace-with-local-model
|
|
||||||
temperature: 0
|
|
||||||
max_tokens: 1024
|
|
||||||
top_p: 1
|
|
||||||
timeout_seconds: 120
|
|
||||||
8
examples/profiles/local/local-proposal.yaml
Normal file
8
examples/profiles/local/local-proposal.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
profiles:
|
||||||
|
local-proposal:
|
||||||
|
backend: local
|
||||||
|
model: replace-with-local-model
|
||||||
|
temperature: 0
|
||||||
|
max_tokens: 2048
|
||||||
|
top_p: 1
|
||||||
|
timeout_seconds: 120
|
||||||
8
examples/profiles/local/local-validation.yaml
Normal file
8
examples/profiles/local/local-validation.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
profiles:
|
||||||
|
local-validation:
|
||||||
|
backend: local
|
||||||
|
model: replace-with-local-model
|
||||||
|
temperature: 0
|
||||||
|
max_tokens: 1024
|
||||||
|
top_p: 1
|
||||||
|
timeout_seconds: 120
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
id: service-proposal-testing
|
|
||||||
backend: service
|
|
||||||
model: replace-with-testing-model
|
|
||||||
temperature: 0
|
|
||||||
max_tokens: 2048
|
|
||||||
top_p: 1
|
|
||||||
timeout_seconds: 120
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
id: service-proposal
|
|
||||||
backend: service
|
|
||||||
model: replace-with-provider-model
|
|
||||||
temperature: 0
|
|
||||||
max_tokens: 2048
|
|
||||||
top_p: 1
|
|
||||||
timeout_seconds: 120
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
id: service-validation-testing
|
|
||||||
backend: service
|
|
||||||
model: replace-with-testing-model
|
|
||||||
temperature: 0
|
|
||||||
max_tokens: 1024
|
|
||||||
top_p: 1
|
|
||||||
timeout_seconds: 120
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
id: service-validation
|
|
||||||
backend: service
|
|
||||||
model: replace-with-provider-model
|
|
||||||
temperature: 0
|
|
||||||
max_tokens: 1024
|
|
||||||
top_p: 1
|
|
||||||
timeout_seconds: 120
|
|
||||||
8
examples/profiles/service/service-proposal-testing.yaml
Normal file
8
examples/profiles/service/service-proposal-testing.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
profiles:
|
||||||
|
service-proposal-testing:
|
||||||
|
backend: service
|
||||||
|
model: replace-with-testing-model
|
||||||
|
temperature: 0
|
||||||
|
max_tokens: 2048
|
||||||
|
top_p: 1
|
||||||
|
timeout_seconds: 120
|
||||||
8
examples/profiles/service/service-proposal.yaml
Normal file
8
examples/profiles/service/service-proposal.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
profiles:
|
||||||
|
service-proposal:
|
||||||
|
backend: service
|
||||||
|
model: replace-with-provider-model
|
||||||
|
temperature: 0
|
||||||
|
max_tokens: 2048
|
||||||
|
top_p: 1
|
||||||
|
timeout_seconds: 120
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
profiles:
|
||||||
|
service-validation-testing:
|
||||||
|
backend: service
|
||||||
|
model: replace-with-testing-model
|
||||||
|
temperature: 0
|
||||||
|
max_tokens: 1024
|
||||||
|
top_p: 1
|
||||||
|
timeout_seconds: 120
|
||||||
8
examples/profiles/service/service-validation.yaml
Normal file
8
examples/profiles/service/service-validation.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
profiles:
|
||||||
|
service-validation:
|
||||||
|
backend: service
|
||||||
|
model: replace-with-provider-model
|
||||||
|
temperature: 0
|
||||||
|
max_tokens: 1024
|
||||||
|
top_p: 1
|
||||||
|
timeout_seconds: 120
|
||||||
17
go.mod
17
go.mod
@@ -1,20 +1,25 @@
|
|||||||
module gitea.maximumdirect.net/eric/audita
|
module gitea.maximumdirect.net/eric/audita/v3
|
||||||
|
|
||||||
go 1.25.5
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.maximumdirect.net/eric/ConfigKit v0.4.2
|
gitea.maximumdirect.net/eric/ConfigKit v0.7.1
|
||||||
gitea.maximumdirect.net/eric/promptkit v0.11.2
|
gitea.maximumdirect.net/eric/ReleaseKit v0.4.1
|
||||||
|
gitea.maximumdirect.net/eric/promptkit v0.14.0
|
||||||
|
golang.org/x/mod v0.40.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2 v2.20260926.0 // indirect
|
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 // indirect
|
||||||
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2 v2.20260926.0 // indirect
|
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||||
|
github.com/yuin/goldmark/v2 v2.1.6 // indirect
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect
|
go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tool gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
|
||||||
|
|||||||
22
go.sum
22
go.sum
@@ -1,11 +1,13 @@
|
|||||||
gitea.maximumdirect.net/eric/ConfigKit v0.4.2 h1:ZMxmv8pOnLtAx2hFTf8Z+pHKUbR89xErkBhwjr4yK24=
|
gitea.maximumdirect.net/eric/ConfigKit v0.7.1 h1:q3HrX4WWCPt1WfMydqFEaAmCf9s6Dq+vknJj0C/mdcI=
|
||||||
gitea.maximumdirect.net/eric/ConfigKit v0.4.2/go.mod h1:tYWpsrbLyu05r8+1MapWp5fN6ZrX9I8hU2xEPedYmYk=
|
gitea.maximumdirect.net/eric/ConfigKit v0.7.1/go.mod h1:C82sbyKHYMV1OwaD2GJanFrwTfcZfjlW2JsGze5b0kA=
|
||||||
gitea.maximumdirect.net/eric/promptkit v0.11.2 h1:2RqI+KtflvSh7vLQaTAQCmMaFQscEA7EPwPazyvXRoY=
|
gitea.maximumdirect.net/eric/ReleaseKit v0.4.1 h1:Qn+mdebYOzCVCEXgCbuGSO/WHmJtItwaQ8e3XwY3qtI=
|
||||||
gitea.maximumdirect.net/eric/promptkit v0.11.2/go.mod h1:onm/FBPUKQ8/NSPwTEk26Ut+dVX2iXcq8vEseB+g/0E=
|
gitea.maximumdirect.net/eric/ReleaseKit v0.4.1/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI=
|
||||||
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2 v2.20260926.0 h1:wdab+R+lgMZxzYwhbAuCrW7zdSQ1cW9ovBR6UzLmZR4=
|
gitea.maximumdirect.net/eric/promptkit v0.14.0 h1:HUzlcrFk18dNGPBgnHqjAw87O0c9Wl/IC1xc/VmzL10=
|
||||||
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2 v2.20260926.0/go.mod h1:a3VBbh9oaGXD6ruxhtwHj+AWW6DY/SSyby1QtMmYSUo=
|
gitea.maximumdirect.net/eric/promptkit v0.14.0/go.mod h1:2YGvNca3bNXZP+e+pJeGzsNUcxwkHvzvuoc+LNNiT3E=
|
||||||
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2 v2.20260926.0 h1:baCCIB6Ee5t0S4Zc++NTBIPcJEv5UPS6DlSrpZfykwg=
|
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 h1:lzfCDbzJQRGzIlRPRprnAV9hVVL5zUQy80VMQ0kbYa0=
|
||||||
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2 v2.20260926.0/go.mod h1:mtgD1E0SGm0e/I8Ffhz+AYX7GN7RKUWJXehNFIKHs4Y=
|
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0/go.mod h1:HjS0f+3q0IdHCTEz+VVAEgIwIWJFAZUjuKdAtvtqKdg=
|
||||||
|
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0 h1:h12ZTRldAmpLAomj0oQRGteFxHLN1LPE6XDl9zJNbm8=
|
||||||
|
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0/go.mod h1:OEFBeKdjTsLzqJwLmXI2O5gG3r64FktcVJVb3mBrRfg=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
@@ -22,8 +24,12 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
|||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
|
github.com/yuin/goldmark/v2 v2.1.6 h1:igy0q9JcqovoFXFx/XjtU8sHVhcUHavPj2sA3EoKJUE=
|
||||||
|
github.com/yuin/goldmark/v2 v2.1.6/go.mod h1:G6M4/qOFtfn01/o14BU1UR2Lo5N3S9Qo7xCuz/sHjGQ=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
|
go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
|
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||||
|
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var systemConfigPaths = []string{"/usr/local/etc/audita/config.yml", "/etc/audita/config.yml"}
|
var systemConfigPaths = []string{"/usr/local/etc/audita/config.yml", "/etc/audita/config.yml"}
|
||||||
@@ -24,6 +24,7 @@ type configInput struct {
|
|||||||
TranscriptPath string
|
TranscriptPath string
|
||||||
GlossaryPath string
|
GlossaryPath string
|
||||||
OutputPath string
|
OutputPath string
|
||||||
|
ReportDirSet bool
|
||||||
Overrides []ck.Override
|
Overrides []ck.Override
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +125,9 @@ func parseConfigInput(command string, args []string, output io.Writer) (configIn
|
|||||||
if f.Name == "config" {
|
if f.Name == "config" {
|
||||||
input.ConfigPathSet = true
|
input.ConfigPathSet = true
|
||||||
}
|
}
|
||||||
|
if f.Name == "report-dir" {
|
||||||
|
input.ReportDirSet = true
|
||||||
|
}
|
||||||
path, ok := settingPaths[f.Name]
|
path, ok := settingPaths[f.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -208,15 +212,22 @@ func configInputError(err error) string {
|
|||||||
return strings.Join(messages, "; ")
|
return strings.Join(messages, "; ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectedReportDir(cfg config.Config, outputPath string) (string, error) {
|
func selectedReportDir(cfg config.Config, outputPath string, reportDirSet bool) (string, error) {
|
||||||
if !cfg.Reporting.Enabled {
|
if !cfg.Reporting.Enabled {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
var reportDir string
|
||||||
if outputPath != "" {
|
if outputPath != "" {
|
||||||
return filepath.Dir(outputPath), nil
|
if reportDirSet {
|
||||||
|
reportDir = cfg.Reporting.StdoutDir
|
||||||
|
} else {
|
||||||
|
reportDir = filepath.Dir(outputPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reportDir = cfg.Reporting.StdoutDir
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cfg.Reporting.StdoutDir) == "" {
|
if strings.TrimSpace(reportDir) == "" {
|
||||||
return "", fmt.Errorf("report directory is required when reporting is enabled and transcript output is stdout")
|
return "", fmt.Errorf("report directory is required when reporting is enabled for the selected destination")
|
||||||
}
|
}
|
||||||
return cfg.Reporting.StdoutDir, nil
|
return reportDir, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSharedConfigInput(t *testing.T) {
|
func TestSharedConfigInput(t *testing.T) {
|
||||||
@@ -215,22 +215,42 @@ func TestReportingAndDebugOverridesRemainIndependent(t *testing.T) {
|
|||||||
t.Fatalf("%s did not honor explicit false: %+v", command, selected.Result.Config)
|
t.Fatalf("%s did not honor explicit false: %+v", command, selected.Result.Config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
processInput, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml", "--config", main, "--overlay", "testing"}, io.Discard)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
processConfig, err := loadConfigInput(processInput, "process")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got, err := selectedReportDir(processConfig.Result.Config, filepath.Join("out", "corrected.json"), processInput.ReportDirSet); err != nil || got != "out" {
|
||||||
|
t.Fatalf("overlay stdout directory redirected file output: %q %v", got, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReportDirectorySelectionOnlyUsesActiveDestination(t *testing.T) {
|
func TestReportDirectorySelectionOnlyUsesActiveDestination(t *testing.T) {
|
||||||
cfg := config.Config{Reporting: config.Reporting{Enabled: true, StdoutDir: "reports"}}
|
cfg := config.Config{Reporting: config.Reporting{Enabled: true, StdoutDir: "reports"}}
|
||||||
if got, err := selectedReportDir(cfg, ""); err != nil || got != "reports" {
|
if got, err := selectedReportDir(cfg, "", false); err != nil || got != "reports" {
|
||||||
t.Fatalf("stdout destination: %q %v", got, err)
|
t.Fatalf("stdout destination: %q %v", got, err)
|
||||||
}
|
}
|
||||||
cfg.Reporting.StdoutDir = ""
|
if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), false); err != nil || got != "out" {
|
||||||
if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json")); err != nil || got != "out" {
|
|
||||||
t.Fatalf("file destination ignored stdout setting: %q %v", got, err)
|
t.Fatalf("file destination ignored stdout setting: %q %v", got, err)
|
||||||
}
|
}
|
||||||
if _, err := selectedReportDir(cfg, ""); err == nil {
|
if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err != nil || got != "reports" {
|
||||||
|
t.Fatalf("explicit file report directory: %q %v", got, err)
|
||||||
|
}
|
||||||
|
cfg.Reporting.StdoutDir = ""
|
||||||
|
if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), false); err != nil || got != "out" {
|
||||||
|
t.Fatalf("inactive blank configured report directory rejected for file output: %q %v", got, err)
|
||||||
|
}
|
||||||
|
if _, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err == nil {
|
||||||
|
t.Fatal("active explicit blank report directory accepted for file output")
|
||||||
|
}
|
||||||
|
if _, err := selectedReportDir(cfg, "", false); err == nil {
|
||||||
t.Fatal("enabled stdout reporting accepted blank destination")
|
t.Fatal("enabled stdout reporting accepted blank destination")
|
||||||
}
|
}
|
||||||
cfg.Reporting.Enabled = false
|
cfg.Reporting.Enabled = false
|
||||||
if got, err := selectedReportDir(cfg, ""); err != nil || got != "" {
|
if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err != nil || got != "" {
|
||||||
t.Fatalf("disabled reporting probed destination: %q %v", got, err)
|
t.Fatalf("disabled reporting probed destination: %q %v", got, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,4 +267,14 @@ func TestBareBooleanFlagsAndReportDirectoryOverride(t *testing.T) {
|
|||||||
if values[ck.Keys("reporting", "enabled").String()] != true || values[ck.Keys("diagnostics", "debug").String()] != true || values[ck.Keys("reporting", "stdout_dir").String()] != "reports" {
|
if values[ck.Keys("reporting", "enabled").String()] != true || values[ck.Keys("diagnostics", "debug").String()] != true || values[ck.Keys("reporting", "stdout_dir").String()] != "reports" {
|
||||||
t.Fatalf("incorrect visited overrides: %+v", values)
|
t.Fatalf("incorrect visited overrides: %+v", values)
|
||||||
}
|
}
|
||||||
|
if !input.ReportDirSet {
|
||||||
|
t.Fatal("explicit --report-dir presence was not recorded")
|
||||||
|
}
|
||||||
|
withoutReportDir, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml"}, io.Discard)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if withoutReportDir.ReportDirSet {
|
||||||
|
t.Fatal("absent --report-dir was recorded as explicit")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,22 +12,22 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/diagnostics"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/diagnostics"
|
||||||
coreio "gitea.maximumdirect.net/eric/audita/internal/core/io"
|
coreio "gitea.maximumdirect.net/eric/audita/v3/internal/core/io"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/normalization"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/normalization"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/outputschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/outputschema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/modules"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/modules"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/processreport"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/processreport"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runProcess(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
func runProcess(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||||
@@ -46,7 +46,7 @@ func runProcess(ctx context.Context, args []string, stdout, stderr io.Writer) in
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
cfg := selected.Result.Config
|
cfg := selected.Result.Config
|
||||||
reportDir, err := selectedReportDir(cfg, input.OutputPath)
|
reportDir, err := selectedReportDir(cfg, input.OutputPath, input.ReportDirSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "audita process: %s\n", llm.RedactError(err, cfg))
|
fmt.Fprintf(stderr, "audita process: %s\n", llm.RedactError(err, cfg))
|
||||||
return 2
|
return 2
|
||||||
@@ -87,7 +87,7 @@ func runProcess(ctx context.Context, args []string, stdout, stderr io.Writer) in
|
|||||||
protected = append(protected, source.Path)
|
protected = append(protected, source.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected = append(protected, runtime.ProfilePaths...)
|
protected = append(protected, runtime.SourcePaths...)
|
||||||
session, err := diagnostics.PrepareOutput(diagnostics.OutputSelection{InputTranscriptPath: input.TranscriptPath, TranscriptPath: input.OutputPath,
|
session, err := diagnostics.PrepareOutput(diagnostics.OutputSelection{InputTranscriptPath: input.TranscriptPath, TranscriptPath: input.OutputPath,
|
||||||
ReportDir: reportDir, ReportingEnabled: cfg.Reporting.Enabled, ProtectedPaths: protected})
|
ReportDir: reportDir, ReportingEnabled: cfg.Reporting.Enabled, ProtectedPaths: protected})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/ReleaseKit/buildinfo"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sourceToCanonicalTranscript(source *schema.SourceTranscript) *schema.Transcript {
|
func sourceToCanonicalTranscript(source *schema.SourceTranscript) *schema.Transcript {
|
||||||
@@ -42,6 +44,9 @@ func Run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
case "config":
|
case "config":
|
||||||
writeConfigUsage(stdout)
|
writeConfigUsage(stdout)
|
||||||
return 0
|
return 0
|
||||||
|
case "version":
|
||||||
|
writeVersionUsage(stdout)
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +61,36 @@ func Run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
if args[0] == "config" {
|
if args[0] == "config" {
|
||||||
return runConfig(ctx, args[1:], stdout, stderr)
|
return runConfig(ctx, args[1:], stdout, stderr)
|
||||||
}
|
}
|
||||||
|
if args[0] == "version" {
|
||||||
|
return runVersion(args[1:], stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintf(stderr, "audita: unknown command %q\n\n", args[0])
|
fmt.Fprintf(stderr, "audita: unknown command %q\n\n", args[0])
|
||||||
writeRootUsage(stderr)
|
writeRootUsage(stderr)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runVersion(args []string, stdout, stderr io.Writer) int {
|
||||||
|
flags := flag.NewFlagSet("version", flag.ContinueOnError)
|
||||||
|
flags.SetOutput(stderr)
|
||||||
|
flags.Usage = func() {} // Route usage after parsing so help stays on stdout.
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
if err == flag.ErrHelp {
|
||||||
|
writeVersionUsage(stdout)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
writeVersionUsage(stderr)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if positional := flags.Args(); len(positional) > 0 {
|
||||||
|
fmt.Fprintf(stderr, "audita version: unexpected argument %q\n\n", positional[0])
|
||||||
|
writeVersionUsage(stderr)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stdout, "audita %s\n", buildinfo.Version())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func runConfig(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
func runConfig(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||||
if len(args) == 0 || isHelpCommand(args) || args[0] == "--help" || args[0] == "-h" {
|
if len(args) == 0 || isHelpCommand(args) || args[0] == "--help" || args[0] == "-h" {
|
||||||
writeConfigUsage(stdout)
|
writeConfigUsage(stdout)
|
||||||
@@ -97,7 +126,7 @@ func runConfigValidate(ctx context.Context, args []string, stdout, stderr io.Wri
|
|||||||
}
|
}
|
||||||
if _, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil); err != nil {
|
if _, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil); err != nil {
|
||||||
fmt.Fprintf(stderr, "audita config validate: %s\n", llm.RedactError(err, selected.Result.Config))
|
fmt.Fprintf(stderr, "audita config validate: %s\n", llm.RedactError(err, selected.Result.Config))
|
||||||
if llm.IsAssetError(err) {
|
if ctx.Err() != nil || llm.IsAssetError(err) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 2
|
return 2
|
||||||
@@ -124,7 +153,7 @@ func runConfigPrintEffective(ctx context.Context, args []string, stdout, stderr
|
|||||||
llmRuntime, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil)
|
llmRuntime, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "audita config print-effective: %s\n", llm.RedactError(err, selected.Result.Config))
|
fmt.Fprintf(stderr, "audita config print-effective: %s\n", llm.RedactError(err, selected.Result.Config))
|
||||||
if llm.IsAssetError(err) {
|
if ctx.Err() != nil || llm.IsAssetError(err) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 2
|
return 2
|
||||||
@@ -177,11 +206,19 @@ func writeRootUsage(w io.Writer) {
|
|||||||
fmt.Fprintln(w, "Commands:")
|
fmt.Fprintln(w, "Commands:")
|
||||||
fmt.Fprintln(w, " process Process a transcript JSON file")
|
fmt.Fprintln(w, " process Process a transcript JSON file")
|
||||||
fmt.Fprintln(w, " config Validate and inspect config")
|
fmt.Fprintln(w, " config Validate and inspect config")
|
||||||
|
fmt.Fprintln(w, " version Print the Audita version")
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
fmt.Fprintln(w, "Example:")
|
fmt.Fprintln(w, "Example:")
|
||||||
fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json")
|
fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeVersionUsage(w io.Writer) {
|
||||||
|
fmt.Fprintln(w, "Print the Audita version.")
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
fmt.Fprintln(w, "Usage:")
|
||||||
|
fmt.Fprintln(w, " audita version")
|
||||||
|
}
|
||||||
|
|
||||||
func writeConfigUsage(w io.Writer) {
|
func writeConfigUsage(w io.Writer) {
|
||||||
fmt.Fprintln(w, "Validate and inspect Audita config.")
|
fmt.Fprintln(w, "Validate and inspect Audita config.")
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
@@ -225,7 +262,7 @@ func writeProcessUsage(w io.Writer) {
|
|||||||
fmt.Fprintf(w, " --%s\n", name)
|
fmt.Fprintf(w, " --%s\n", name)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(w, " --reporting[=true|false] Write the three-file report bundle (default true)")
|
fmt.Fprintln(w, " --reporting[=true|false] Write the three-file report bundle (default true)")
|
||||||
fmt.Fprintln(w, " --report-dir DIR Report directory for stdout output only; ignored with --output or --reporting=false")
|
fmt.Fprintln(w, " --report-dir DIR Select the report directory (stdout setting for stdout; output parent by default for files)")
|
||||||
fmt.Fprintln(w, " --debug[=true|false] Persist detailed diagnostics (default false)")
|
fmt.Fprintln(w, " --debug[=true|false] Persist detailed diagnostics (default false)")
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
fmt.Fprintln(w, "Example:")
|
fmt.Fprintln(w, "Example:")
|
||||||
|
|||||||
76
internal/cli/runtime_errors_test.go
Normal file
76
internal/cli/runtime_errors_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeRuntimeErrorConfig(t *testing.T, operatorDocument string) string {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
profiles := filepath.Join(dir, "profiles")
|
||||||
|
if err := os.Mkdir(profiles, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
main := filepath.Join(dir, "config.yml")
|
||||||
|
configText := "version: 2\nprofiles_dir: profiles\nbackends:\n offline:\n endpoint: http://127.0.0.1:1/v1\n credential: AUDITA_TEST_PROVIDER_KEY\nllm:\n proposal_profile: proposal\n"
|
||||||
|
if err := os.WriteFile(main, []byte(configText), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(profiles, "operator.yml"), []byte(operatorDocument), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return main
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigCommandsClassifyOperatorPromptErrorsAsInputErrors(t *testing.T) {
|
||||||
|
t.Setenv("AUDITA_TEST_PROVIDER_KEY", "synthetic-cli-redaction-secret")
|
||||||
|
definitions := map[string]string{
|
||||||
|
"malformed operator YAML": "profiles:\n proposal: [\n",
|
||||||
|
"invalid operator template": "profiles:\n proposal:\n backend: offline\n model: local-model\nprompts:\n modules.grammar.proposal:\n v1:\n messages:\n - role: user\n content: '{{'\n",
|
||||||
|
"missing operator message resource": "profiles:\n proposal:\n backend: offline\n model: local-model\nprompts:\n modules.grammar.proposal:\n v1:\n messages:\n - role: user\n content_file: missing-instruction.md\n",
|
||||||
|
}
|
||||||
|
commands := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{name: "validate", args: []string{"config", "validate"}},
|
||||||
|
{name: "print effective", args: []string{"config", "print-effective", "--view", "profiles"}},
|
||||||
|
}
|
||||||
|
for definition, document := range definitions {
|
||||||
|
for _, command := range commands {
|
||||||
|
t.Run(definition+"/"+command.name, func(t *testing.T) {
|
||||||
|
main := writeRuntimeErrorConfig(t, document)
|
||||||
|
args := append(append([]string(nil), command.args...), "--config", main)
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if code := Run(context.Background(), args, &stdout, &stderr); code != 2 {
|
||||||
|
t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
if strings.Contains(stderr.String(), "synthetic-cli-redaction-secret") {
|
||||||
|
t.Fatalf("configured secret leaked: %q", stderr.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigCommandsKeepCallerCancellationAsRuntimeExit(t *testing.T) {
|
||||||
|
t.Setenv("AUDITA_TEST_PROVIDER_KEY", "synthetic-cli-redaction-secret")
|
||||||
|
main := writeRuntimeErrorConfig(t, "profiles:\n proposal:\n backend: offline\n model: local-model\n")
|
||||||
|
commands := [][]string{
|
||||||
|
{"config", "validate", "--config", main},
|
||||||
|
{"config", "print-effective", "--config", main, "--view", "profiles"},
|
||||||
|
}
|
||||||
|
for _, args := range commands {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if code := Run(ctx, args, &stdout, &stderr); code != 1 {
|
||||||
|
t.Fatalf("args=%v exit=%d, stdout=%q, stderr=%q", args, code, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
internal/cli/version_test.go
Normal file
73
internal/cli/version_test.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/buildinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionCommandRunsWithoutApplicationSetup(t *testing.T) {
|
||||||
|
t.Chdir(t.TempDir())
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if code := Run(context.Background(), []string{"version"}, &stdout, &stderr); code != 0 {
|
||||||
|
t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
if got, want := stdout.String(), fmt.Sprintf("audita %s\n", buildinfo.Version()); got != want {
|
||||||
|
t.Fatalf("stdout=%q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if got := stderr.String(); got != "" {
|
||||||
|
t.Fatalf("stderr=%q, want empty", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionCommandHelp(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "root help", args: []string{"--help"}, want: "version Print the Audita version"},
|
||||||
|
{name: "help version", args: []string{"help", "version"}, want: "audita version"},
|
||||||
|
{name: "short flag", args: []string{"version", "-h"}, want: "audita version"},
|
||||||
|
{name: "long flag", args: []string{"version", "--help"}, want: "audita version"},
|
||||||
|
{name: "parser help", args: []string{"version", "--h"}, want: "audita version"},
|
||||||
|
{name: "help short circuits", args: []string{"version", "-h", "--help", "extra"}, want: "audita version"},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if code := Run(context.Background(), test.args, &stdout, &stderr); code != 0 {
|
||||||
|
t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), test.want) {
|
||||||
|
t.Fatalf("stdout=%q, want %q", stdout.String(), test.want)
|
||||||
|
}
|
||||||
|
if got := stderr.String(); got != "" {
|
||||||
|
t.Fatalf("stderr=%q, want empty", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionCommandRejectsUnexpectedArguments(t *testing.T) {
|
||||||
|
for _, args := range [][]string{
|
||||||
|
{"version", "extra"},
|
||||||
|
{"version", "--unknown"},
|
||||||
|
} {
|
||||||
|
t.Run(strings.Join(args, " "), func(t *testing.T) {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if code := Run(context.Background(), args, &stdout, &stderr); code != 2 {
|
||||||
|
t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
if got := stdout.String(); got != "" {
|
||||||
|
t.Fatalf("stdout=%q, want empty", got)
|
||||||
|
}
|
||||||
|
if got := stderr.String(); got == "" {
|
||||||
|
t.Fatal("expected usage error on stderr")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package chunking
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Section represents a contiguous chunk of transcript segments with metadata.
|
// Section represents a contiguous chunk of transcript segments with metadata.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mapTokenEstimator provides deterministic per-segment token counts for tests.
|
// mapTokenEstimator provides deterministic per-segment token counts for tests.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package chunking
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestComputeSummary(t *testing.T) {
|
func TestComputeSummary(t *testing.T) {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/outputschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/outputschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -137,9 +137,167 @@ func TestSurvivingCredentials(t *testing.T) {
|
|||||||
if err != nil || loaded.Config.Backends["other"].Credential != nil {
|
if err != nil || loaded.Config.Backends["other"].Credential != nil {
|
||||||
t.Fatalf("null credential: %+v %v", loaded, err)
|
t.Fatalf("null credential: %+v %v", loaded, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inherited := `version: 2
|
||||||
|
backends:
|
||||||
|
parent:
|
||||||
|
endpoint: https://parent.invalid/v1
|
||||||
|
credential: BASE_SECRET
|
||||||
|
local:
|
||||||
|
extends: parent
|
||||||
|
credential: null
|
||||||
|
llm:
|
||||||
|
proposal_profile: proposal
|
||||||
|
`
|
||||||
|
if _, err := loadText(inherited, ""); err == nil {
|
||||||
|
t.Fatal("null child credential removed the unselected parent's surviving reference")
|
||||||
|
}
|
||||||
|
t.Setenv("BASE_SECRET", "synthetic-parent-secret")
|
||||||
|
loaded, err = loadText(strings.Replace(inherited, " credential: null\n", "", 1), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := loaded.Config.Backends["local"].Credential.Value(); got != "synthetic-parent-secret" {
|
||||||
|
t.Fatalf("inherited secret: %q", got)
|
||||||
|
}
|
||||||
|
if got := loaded.Metadata().IsExplicit(ck.Keys("backends", "local", "credential")); !got {
|
||||||
|
t.Fatal("inherited credential is not explicit")
|
||||||
|
}
|
||||||
|
var inheritedEffective bytes.Buffer
|
||||||
|
if err := loaded.WriteEffective(&inheritedEffective); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(inheritedEffective.String(), "synthetic-parent-secret") {
|
||||||
|
t.Fatal("effective output leaked an inherited secret")
|
||||||
|
}
|
||||||
|
loaded, err = loadText(inherited, "")
|
||||||
|
if err != nil || loaded.Config.Backends["local"].Credential != nil {
|
||||||
|
t.Fatalf("child null credential: %+v %v", loaded, err)
|
||||||
|
}
|
||||||
|
if got := loaded.Config.Backends["parent"].Credential.Value(); got != "synthetic-parent-secret" {
|
||||||
|
t.Fatalf("parent credential after child null: %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
t.Setenv("AUDITA_MODEL", "legacy-model")
|
t.Setenv("AUDITA_MODEL", "legacy-model")
|
||||||
loaded, err = loadText(base, "")
|
loaded, err = loadText(base, "")
|
||||||
if err != nil || loaded.Config.LLM.ProposalProfile != "proposal" {
|
if err != nil || loaded.Config.LLM.ProposalProfile != "proposal" {
|
||||||
t.Fatalf("legacy environment affected loading: %+v %v", loaded, err)
|
t.Fatalf("legacy environment affected loading: %+v %v", loaded, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSiblingInheritancePreservesBackendSettings(t *testing.T) {
|
||||||
|
text := `version: 2
|
||||||
|
backends:
|
||||||
|
base:
|
||||||
|
endpoint: https://base.invalid/v1
|
||||||
|
concurrency: 3
|
||||||
|
queue_capacity: 0
|
||||||
|
local:
|
||||||
|
extends: base
|
||||||
|
llm:
|
||||||
|
proposal_profile: proposal
|
||||||
|
`
|
||||||
|
loaded, err := loadText(text, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
backend := loaded.Config.Backends["local"]
|
||||||
|
if backend.Endpoint != "https://base.invalid/v1" || backend.Concurrency != 3 || backend.QueueCapacity != 0 {
|
||||||
|
t.Fatalf("inherited backend settings: %+v", backend)
|
||||||
|
}
|
||||||
|
for _, path := range []ck.Path{
|
||||||
|
ck.Keys("backends", "local", "endpoint"),
|
||||||
|
ck.Keys("backends", "local", "concurrency"),
|
||||||
|
ck.Keys("backends", "local", "queue_capacity"),
|
||||||
|
} {
|
||||||
|
if !loaded.Metadata().IsExplicit(path) {
|
||||||
|
t.Errorf("inherited field %s is not explicit", path.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overridden, err := loadText(strings.Replace(text, " extends: base\n", " extends: base\n concurrency: 2\n", 1), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := overridden.Config.Backends["local"].Concurrency; got != 2 {
|
||||||
|
t.Fatalf("child concurrency override: %d", got)
|
||||||
|
}
|
||||||
|
if got := overridden.Config.Backends["local"].QueueCapacity; got != 0 {
|
||||||
|
t.Fatalf("inherited zero queue capacity: %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSiblingInheritanceUsesSelectedOverlay(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := os.Mkdir(filepath.Join(dir, "overlays"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
main := filepath.Join(dir, "config.yml")
|
||||||
|
text := `version: 2
|
||||||
|
backends:
|
||||||
|
base:
|
||||||
|
endpoint: https://base.invalid/v1
|
||||||
|
concurrency: 3
|
||||||
|
queue_capacity: 0
|
||||||
|
local:
|
||||||
|
extends: base
|
||||||
|
concurrency: 2
|
||||||
|
llm:
|
||||||
|
proposal_profile: proposal
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(main, []byte(text), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "overlays", "testing.yml"), []byte("backends:\n base:\n endpoint: https://overlay.invalid/v1\n concurrency: 4\n"), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loaded, err := Load(ck.File(main), "testing")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
child := loaded.Config.Backends["local"]
|
||||||
|
if child.Endpoint != "https://overlay.invalid/v1" || child.Concurrency != 2 || child.QueueCapacity != 0 {
|
||||||
|
t.Fatalf("overlay inheritance and child override: %+v", child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSiblingInheritanceDoesNotBypassBackendValidation(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
text string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid inherited parent setting",
|
||||||
|
text: `version: 2
|
||||||
|
backends:
|
||||||
|
base:
|
||||||
|
endpoint: https://base.invalid/v1
|
||||||
|
concurrency: 0
|
||||||
|
local:
|
||||||
|
extends: base
|
||||||
|
llm:
|
||||||
|
proposal_profile: proposal
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid child override",
|
||||||
|
text: `version: 2
|
||||||
|
backends:
|
||||||
|
base:
|
||||||
|
endpoint: https://base.invalid/v1
|
||||||
|
concurrency: 3
|
||||||
|
local:
|
||||||
|
extends: base
|
||||||
|
concurrency: 0
|
||||||
|
llm:
|
||||||
|
proposal_profile: proposal
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if _, err := loadText(tc.text, ""); err == nil {
|
||||||
|
t.Fatal("invalid backend concurrency accepted")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/normalization"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/normalization"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunDirectory represents a per-run diagnostics directory
|
// RunDirectory represents a per-run diagnostics directory
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package normalization
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NormalizationConfig holds configuration parameters for transcript normalization
|
// NormalizationConfig holds configuration parameters for transcript normalization
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNormalizationNoOp(t *testing.T) {
|
func TestNormalizationNoOp(t *testing.T) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tinyTranscript() *schema.Transcript {
|
func tinyTranscript() *schema.Transcript {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/diagnostics"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/diagnostics"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStringRedactionPreservesSerializationAndDomainData(t *testing.T) {
|
func TestStringRedactionPreservesSerializationAndDomainData(t *testing.T) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompletionLine is the brief successful CLI summary. It uses the same counts
|
// CompletionLine is the brief successful CLI summary. It uses the same counts
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testChunkProposalHarness is a test-only helper that composes existing
|
// testChunkProposalHarness is a test-only helper that composes existing
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StructuredLLMClient = structuredoutput.Client
|
type StructuredLLMClient = structuredoutput.Client
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeLLMClient struct{}
|
type fakeLLMClient struct{}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,23 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRolesShareBackendCapacityAndReleaseAfterCancellation(t *testing.T) {
|
func TestRolesShareBackendCapacityAndReleaseAfterCancellation(t *testing.T) {
|
||||||
|
t.Setenv("INHERITED_TEST_KEY", "synthetic-inherited-runtime-secret")
|
||||||
|
t.Setenv("OPERATOR_BACKEND_KEY", "synthetic-operator-backend-secret")
|
||||||
entered := make(chan struct{}, 3)
|
entered := make(chan struct{}, 3)
|
||||||
release := make(chan struct{})
|
release := make(chan struct{})
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if got := r.Header.Get("Authorization"); got != "Bearer synthetic-inherited-runtime-secret" {
|
||||||
|
t.Errorf("inherited credential header: %q", got)
|
||||||
|
}
|
||||||
entered <- struct{}{}
|
entered <- struct{}{}
|
||||||
<-release
|
<-release
|
||||||
content, _ := json.Marshal(`{"corrections":[]}`)
|
content, _ := json.Marshal(`{"corrections":[]}`)
|
||||||
@@ -37,16 +42,26 @@ func TestRolesShareBackendCapacityAndReleaseAfterCancellation(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
main := filepath.Join(dir, "config.yml")
|
main := filepath.Join(dir, "config.yml")
|
||||||
body := fmt.Sprintf("version: 2\nbackends:\n shared:\n endpoint: %s/v1\n concurrency: 1\n queue_capacity: 0\nllm:\n proposal_profile: proposal\n validation_profile: validation\n transient_retries: 0\n repair_attempts: 0\n", server.URL)
|
body := fmt.Sprintf("version: 2\nbackends:\n shared-base:\n endpoint: %s/v1\n credential: INHERITED_TEST_KEY\n concurrency: 1\n queue_capacity: 0\n shared:\n extends: shared-base\nllm:\n proposal_profile: proposal\n validation_profile: validation\n transient_retries: 0\n repair_attempts: 0\n", server.URL)
|
||||||
if err := os.WriteFile(main, []byte(body), 0600); err != nil {
|
if err := os.WriteFile(main, []byte(body), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, name := range []string{"proposal", "validation"} {
|
for _, name := range []string{"proposal", "validation"} {
|
||||||
profile := "id: " + name + "\nbackend: shared\nmodel: local\n"
|
profile := "profiles:\n " + name + ":\n backend: shared\n model: local\n"
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", name+".yaml"), []byte(profile), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", name+".yaml"), []byte(profile), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
operatorBackend := `backends:
|
||||||
|
shared:
|
||||||
|
endpoint: https://operator.invalid/v1
|
||||||
|
concurrency_limit: 77
|
||||||
|
queue_capacity: 88
|
||||||
|
api_key_env: OPERATOR_BACKEND_KEY
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "backend-tuning.yaml"), []byte(operatorBackend), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
loaded, err := config.Load(ck.File(main), "")
|
loaded, err := config.Load(ck.File(main), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -171,7 +186,7 @@ func TestDistinctBackendsHaveIndependentCapacity(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for name, backend := range map[string]string{"proposal": "first", "validation": "second"} {
|
for name, backend := range map[string]string{"proposal": "first", "validation": "second"} {
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", name+".yaml"), []byte("id: "+name+"\nbackend: "+backend+"\nmodel: local\n"), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", name+".yaml"), []byte("profiles:\n "+name+":\n backend: "+backend+"\n model: local\n"), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +30,22 @@ type reply struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func adapterFixture(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request)) (*Runtime, *atomic.Int32, string) {
|
func adapterFixture(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request)) (*Runtime, *atomic.Int32, string) {
|
||||||
|
return adapterFixtureWithDocuments(t, replies, retries, repairs, hook, "offline", map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func adapterFixtureWithPrompt(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request), operatorPrompt string) (*Runtime, *atomic.Int32, string) {
|
||||||
|
documents := map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n",
|
||||||
|
}
|
||||||
|
if operatorPrompt != "" {
|
||||||
|
documents["operator-prompt.yaml"] = operatorPrompt
|
||||||
|
}
|
||||||
|
return adapterFixtureWithDocuments(t, replies, retries, repairs, hook, "offline", documents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adapterFixtureWithDocuments(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request), backendID string, documents map[string]string) (*Runtime, *atomic.Int32, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var count atomic.Int32
|
var count atomic.Int32
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -65,13 +81,21 @@ func adapterFixture(t *testing.T, replies []reply, retries, repairs int, hook fu
|
|||||||
}))
|
}))
|
||||||
t.Cleanup(server.Close)
|
t.Cleanup(server.Close)
|
||||||
t.Setenv("ADAPTER_KEY", "adapter-secret")
|
t.Setenv("ADAPTER_KEY", "adapter-secret")
|
||||||
base := fmt.Sprintf("version: 2\nbackends:\n offline:\n endpoint: %s/v1\n credential: ADAPTER_KEY\nllm:\n proposal_profile: base\n transient_retries: %d\n repair_attempts: %d\n", server.URL, retries, repairs)
|
dir := t.TempDir()
|
||||||
dir := profileLayout(t, base, "")
|
profileDirectory := filepath.Join(dir, "profiles")
|
||||||
main := filepath.Join(dir, "config.yml")
|
if err := os.Mkdir(profileDirectory, 0700); err != nil {
|
||||||
profile := "id: base\nbackend: offline\nmodel: full-model\n"
|
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "base.yaml"), []byte(profile), 0600); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
base := fmt.Sprintf("version: 2\nprofiles_dir: profiles\nbackends:\n %s:\n endpoint: %s/v1\n credential: ADAPTER_KEY\n concurrency: 1\n queue_capacity: 0\nllm:\n proposal_profile: base\n transient_retries: %d\n repair_attempts: %d\n", backendID, server.URL, retries, repairs)
|
||||||
|
main := filepath.Join(dir, "config.yml")
|
||||||
|
if err := os.WriteFile(main, []byte(base), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, content := range documents {
|
||||||
|
if err := os.WriteFile(filepath.Join(profileDirectory, name), []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
loaded, err := config.Load(ck.File(main), "")
|
loaded, err := config.Load(ck.File(main), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -83,6 +107,10 @@ func adapterFixture(t *testing.T, replies []reply, retries, repairs int, hook fu
|
|||||||
return runtime, &count, dir
|
return runtime, &count, dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func operatorPromptDocument(system, user string) string {
|
||||||
|
return fmt.Sprintf("prompts:\n %s:\n v1:\n messages:\n - role: system\n content: %q\n - role: user\n content: %q\n output:\n format: text\n validation_mode: basic\n", prompts.PromptIDModuleGlossaryProposal, system, user)
|
||||||
|
}
|
||||||
|
|
||||||
func adapterRequest(schema responseschema.Key) structuredoutput.Request {
|
func adapterRequest(schema responseschema.Key) structuredoutput.Request {
|
||||||
id := prompts.PromptIDModuleGlossaryProposal
|
id := prompts.PromptIDModuleGlossaryProposal
|
||||||
vars := map[string]string{"TranscriptDescriptionBlock": "", "GlossaryJSON": "[]", "SectionJSON": "[]"}
|
vars := map[string]string{"TranscriptDescriptionBlock": "", "GlossaryJSON": "[]", "SectionJSON": "[]"}
|
||||||
@@ -286,10 +314,238 @@ func TestPreparedAdapterMetadataAndTargetGuard(t *testing.T) {
|
|||||||
if !found {
|
if !found {
|
||||||
t.Fatal("hardening policy missing")
|
t.Fatal("hardening policy missing")
|
||||||
}
|
}
|
||||||
|
backend := runtime.Config.Backends["offline"]
|
||||||
|
backend.Endpoint = "http://127.0.0.1:1/v1"
|
||||||
|
runtime.Config.Backends["offline"] = backend
|
||||||
|
if _, err := client.CompleteStructured(context.Background(), req, &out); err == nil || !strings.Contains(err.Error(), "target changed") || count.Load() != 1 {
|
||||||
|
t.Fatalf("changed application backend target dispatched: %v, %d", err, count.Load())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOperatorPromptOverridesUsePerCallSchemaAndOptionalSharedText(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
system string
|
||||||
|
user string
|
||||||
|
replies []reply
|
||||||
|
repairs int
|
||||||
|
wantSystem string
|
||||||
|
wantUser string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "operator text without shared references",
|
||||||
|
system: "Operator system instructions.",
|
||||||
|
user: "Operator request without variables.",
|
||||||
|
replies: []reply{{200, `{"wrong":true}`}, {200, `{"corrections":[]}`}},
|
||||||
|
repairs: 1,
|
||||||
|
wantSystem: "Operator system instructions.",
|
||||||
|
wantUser: "Operator request without variables.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "operator text with shared references",
|
||||||
|
system: "Policy: {{.HardeningPolicy}}",
|
||||||
|
user: "Section: {{.SectionJSON}}",
|
||||||
|
replies: []reply{{200, `{"corrections":[]}`}},
|
||||||
|
wantSystem: "Policy: " + prompts.HardeningText(),
|
||||||
|
wantUser: "Section: []",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var posted []struct {
|
||||||
|
Messages []structuredoutput.Message `json:"messages"`
|
||||||
|
}
|
||||||
|
runtime, count, _ := adapterFixtureWithPrompt(t, tc.replies, 0, tc.repairs, func(r *http.Request) {
|
||||||
|
var envelope struct {
|
||||||
|
Messages []structuredoutput.Message `json:"messages"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&envelope); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
posted = append(posted, envelope)
|
||||||
|
}, operatorPromptDocument(tc.system, tc.user))
|
||||||
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req := adapterRequest(responseschema.CorrectionSetKey)
|
||||||
|
if tc.name == "operator text with shared references" {
|
||||||
|
req.Variables["HardeningPolicy"] = "attacker-controlled"
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
response, err := client.CompleteStructured(context.Background(), req, &out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if int(count.Load()) != len(tc.replies) || response.Execution.StructuralRepairs != tc.repairs {
|
||||||
|
t.Fatalf("schema/repair execution = %d calls, %+v; want %d calls and %d repair", count.Load(), response.Execution, len(tc.replies), tc.repairs)
|
||||||
|
}
|
||||||
|
if response.Prepared == nil || len(posted) == 0 || len(posted[0].Messages) != len(response.Prepared.Messages) {
|
||||||
|
t.Fatal("prepared operator prompt was not dispatched")
|
||||||
|
}
|
||||||
|
for i := range response.Prepared.Messages {
|
||||||
|
if response.Prepared.Messages[i] != posted[0].Messages[i] {
|
||||||
|
t.Fatal("prepared operator messages differ from the first dispatched request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(response.Prepared.Messages) != 2 || response.Prepared.Messages[0].Content != tc.wantSystem || response.Prepared.Messages[1].Content != tc.wantUser {
|
||||||
|
t.Fatalf("prepared messages = %+v", response.Prepared.Messages)
|
||||||
|
}
|
||||||
|
if tc.name == "operator text with shared references" {
|
||||||
|
if strings.Contains(response.Prepared.Messages[0].Content, "attacker-controlled") || !strings.Contains(response.Prepared.Messages[0].Content, prompts.HardeningText()) {
|
||||||
|
t.Fatal("caller variables replaced the reserved shared value")
|
||||||
|
}
|
||||||
|
} else if strings.Contains(response.Prepared.Messages[0].Content+response.Prepared.Messages[1].Content, prompts.HardeningText()) {
|
||||||
|
t.Fatal("shared text was injected into operator messages that omit its reference")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfilePresenceBackendAuthorityAndProviderParametersReachWire(t *testing.T) {
|
||||||
|
t.Setenv("CATALOG_TRAP_KEY", "catalog-secret-must-not-be-used")
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
parentTemperature string
|
||||||
|
childTemperature string
|
||||||
|
wantTemperature bool
|
||||||
|
}{
|
||||||
|
{name: "explicit zero", parentTemperature: " temperature: 0.8\n", childTemperature: " temperature: 0\n", wantTemperature: true},
|
||||||
|
{name: "omitted", wantTemperature: false},
|
||||||
|
{name: "null clears inherited control", parentTemperature: " temperature: 0.8\n", childTemperature: " temperature: null\n", wantTemperature: false},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
profileDocument := "profiles:\n \"parent~/partial\":\n backend: openrouter\n model: parent-model\n" + tc.parentTemperature +
|
||||||
|
" base:\n extends: \"parent~/partial\"\n model: full-model\n timeout_seconds: 0\n" + tc.childTemperature +
|
||||||
|
" extra_params:\n routing:\n overridden: profile\n profile_only: retained\n cleared: null\n"
|
||||||
|
operatorBackend := `backends:
|
||||||
|
openrouter:
|
||||||
|
endpoint: https://operator.invalid/v1
|
||||||
|
concurrency_limit: 91
|
||||||
|
queue_capacity: 92
|
||||||
|
api_key_env: CATALOG_TRAP_KEY
|
||||||
|
extra_params:
|
||||||
|
routing:
|
||||||
|
inherited: backend
|
||||||
|
overridden: backend
|
||||||
|
cleared: remove-me
|
||||||
|
`
|
||||||
|
documents := map[string]string{
|
||||||
|
"unrelated-profile-file.yml": profileDocument,
|
||||||
|
"provider-tuning.yaml": operatorBackend,
|
||||||
|
}
|
||||||
|
var wire map[string]json.RawMessage
|
||||||
|
runtime, count, _ := adapterFixtureWithDocuments(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, func(r *http.Request) {
|
||||||
|
if got := r.Header.Get("Authorization"); got != "Bearer adapter-secret" {
|
||||||
|
t.Errorf("application credential was not authoritative: %q", got)
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&wire); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}, "openrouter", documents)
|
||||||
|
profile := runtime.Profiles["proposal"]
|
||||||
|
if profile.EffectiveModelParams.APIKeyEnv != "" || profile.EffectiveModelParams.Endpoint == "https://operator.invalid/v1" || profile.EffectiveModelParams.TimeoutSeconds != 0 {
|
||||||
|
t.Fatalf("application backend mapping or zero timeout lost: %+v", profile.EffectiveModelParams)
|
||||||
|
}
|
||||||
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
response, err := client.CompleteStructured(context.Background(), adapterRequest(responseschema.CorrectionSetKey), &out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if count.Load() != 1 {
|
||||||
|
t.Fatalf("provider calls = %d, want 1", count.Load())
|
||||||
|
}
|
||||||
|
temperature, hasTemperature := wire["temperature"]
|
||||||
|
if hasTemperature != tc.wantTemperature {
|
||||||
|
t.Fatalf("temperature presence = %t, want %t: %s", hasTemperature, tc.wantTemperature, wire)
|
||||||
|
}
|
||||||
|
if tc.wantTemperature {
|
||||||
|
var value float64
|
||||||
|
if err := json.Unmarshal(temperature, &value); err != nil || value != 0 {
|
||||||
|
t.Fatalf("explicit zero temperature = %s (%v)", temperature, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var routing map[string]any
|
||||||
|
if err := json.Unmarshal(wire["routing"], &routing); err != nil {
|
||||||
|
t.Fatalf("provider routing fields missing: %s (%v)", wire, err)
|
||||||
|
}
|
||||||
|
if routing["inherited"] != "backend" || routing["overridden"] != "profile" || routing["profile_only"] != "retained" {
|
||||||
|
t.Fatalf("recursively overlaid provider fields = %#v", routing)
|
||||||
|
}
|
||||||
|
if value, exists := routing["cleared"]; !exists || value != nil {
|
||||||
|
t.Fatalf("null did not replace inherited provider field: %#v", routing)
|
||||||
|
}
|
||||||
|
if response.Model != "full-model" || response.Prepared == nil {
|
||||||
|
t.Fatalf("execution did not use selected profile: %+v", response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparedAdapterUsesCapturedProfileAndPromptUntilReconstructed(t *testing.T) {
|
||||||
|
var posted []struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []structuredoutput.Message `json:"messages"`
|
||||||
|
}
|
||||||
|
runtime, count, dir := adapterFixtureWithPrompt(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, func(r *http.Request) {
|
||||||
|
var envelope struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []structuredoutput.Message `json:"messages"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&envelope); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
posted = append(posted, envelope)
|
||||||
|
}, operatorPromptDocument("Initial operator instructions.", "Initial payload."))
|
||||||
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
profile := "profiles:\n base:\n backend: offline\n model: edited-model\n"
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "definitions.yaml"), []byte(profile), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
updatedPrompt := operatorPromptDocument("Edited operator instructions.", "Edited payload.")
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "operator-prompt.yaml"), []byte(updatedPrompt), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out map[string]any
|
||||||
|
response, err := client.CompleteStructured(context.Background(), adapterRequest(responseschema.CorrectionSetKey), &out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if response.Model != "full-model" || response.Prepared == nil || response.Prepared.Messages[0].Content != "Initial operator instructions." || posted[0].Model != "full-model" {
|
||||||
|
t.Fatalf("existing runtime did not retain its captured configuration: response=%+v request=%+v", response, posted[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
main := filepath.Join(dir, "config.yml")
|
||||||
|
newRuntime, err := NewRuntime(context.Background(), runtime.Config, main, http.DefaultClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
newClient, err := newRuntime.NewRoleClient("proposal", CallHooks{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
response, err = newClient.CompleteStructured(context.Background(), adapterRequest(responseschema.CorrectionSetKey), &out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if int(count.Load()) != 2 || response.Model != "edited-model" || response.Prepared == nil || response.Prepared.Messages[0].Content != "Edited operator instructions." || posted[1].Model != "edited-model" {
|
||||||
|
t.Fatalf("new runtime did not capture edited configuration: response=%+v request=%+v", response, posted[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreparedAdapterRejectsTargetDriftAndPreparationErrors(t *testing.T) {
|
func TestPreparedAdapterRejectsTargetDriftAndPreparationErrors(t *testing.T) {
|
||||||
runtime, count, dir := adapterFixture(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, nil)
|
runtime, count, _ := adapterFixture(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, nil)
|
||||||
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -300,15 +556,6 @@ func TestPreparedAdapterRejectsTargetDriftAndPreparationErrors(t *testing.T) {
|
|||||||
if _, err := client.CompleteStructured(context.Background(), req, &out); err == nil || count.Load() != 0 {
|
if _, err := client.CompleteStructured(context.Background(), req, &out); err == nil || count.Load() != 0 {
|
||||||
t.Fatalf("preparation failure dispatched: %v, %d", err, count.Load())
|
t.Fatalf("preparation failure dispatched: %v, %d", err, count.Load())
|
||||||
}
|
}
|
||||||
// PrepareExecution reloads the profile, so a changed target must be blocked.
|
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "base.yaml"), []byte("id: base\nbackend: offline\nendpoint: http://127.0.0.1:1/v1\nmodel: full-model\n"), 0600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
req = adapterRequest(responseschema.CorrectionSetKey)
|
|
||||||
_, err = client.CompleteStructured(context.Background(), req, &out)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "target changed") || count.Load() != 0 {
|
|
||||||
t.Fatalf("target drift dispatched: %v, %d", err, count.Load())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreparedAdapterCancellationStopsRetryWait(t *testing.T) {
|
func TestPreparedAdapterCancellationStopsRetryWait(t *testing.T) {
|
||||||
@@ -333,7 +580,9 @@ func TestPreparedAdapterCancellationStopsRetryWait(t *testing.T) {
|
|||||||
|
|
||||||
func TestPreparedAdapterDistinguishesOperationTimeoutFromParentCancellation(t *testing.T) {
|
func TestPreparedAdapterDistinguishesOperationTimeoutFromParentCancellation(t *testing.T) {
|
||||||
blocking := func(*http.Request) { time.Sleep(500 * time.Millisecond) }
|
blocking := func(*http.Request) { time.Sleep(500 * time.Millisecond) }
|
||||||
runtime, _, _ := adapterFixture(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, blocking)
|
runtime, _, _ := adapterFixtureWithDocuments(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, blocking, "offline", map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n timeout_seconds: 0\n",
|
||||||
|
})
|
||||||
runtime.Config.LLM.OperationTimeout = 200 * time.Millisecond
|
runtime.Config.LLM.OperationTimeout = 200 * time.Millisecond
|
||||||
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stageSanitizer = regexp.MustCompile(`[^a-zA-Z0-9_.-]+`)
|
var stageSanitizer = regexp.MustCompile(`[^a-zA-Z0-9_.-]+`)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/responseschema"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,9 +24,9 @@ type Runtime struct {
|
|||||||
Config config.Config
|
Config config.Config
|
||||||
Engine *pk.Engine
|
Engine *pk.Engine
|
||||||
Profiles map[string]pk.ProfileInspection
|
Profiles map[string]pk.ProfileInspection
|
||||||
// ProfilePaths contains filesystem sources actually read by PromptKit.
|
// SourcePaths contains host configuration and message files captured by PromptKit.
|
||||||
ProfilePaths []string
|
SourcePaths []string
|
||||||
nextCallID atomic.Uint64
|
nextCallID atomic.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetError indicates a broken embedded prompt or schema rather than an
|
// AssetError indicates a broken embedded prompt or schema rather than an
|
||||||
@@ -39,32 +37,63 @@ func (e *AssetError) Error() string { return e.Err.Error() }
|
|||||||
func (e *AssetError) Unwrap() error { return e.Err }
|
func (e *AssetError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *http.Client) (*Runtime, error) {
|
func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *http.Client) (*Runtime, error) {
|
||||||
|
return newRuntimeWithPromptFS(ctx, c, mainPath, client, prompts.Filesystem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuntimeWithPromptFS(ctx context.Context, c config.Config, mainPath string, client *http.Client, promptFS fs.FS) (*Runtime, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
profileDir := c.ProfilesDir
|
profileDir := c.ProfilesDir
|
||||||
if !filepath.IsAbs(profileDir) {
|
if !filepath.IsAbs(profileDir) {
|
||||||
profileDir = filepath.Join(filepath.Dir(mainPath), profileDir)
|
profileDir = filepath.Join(filepath.Dir(mainPath), profileDir)
|
||||||
}
|
}
|
||||||
profileFS := &profileSourceFS{FS: os.DirFS(profileDir), paths: make(map[string]struct{})}
|
promptInputs, err := pk.FilesystemInputs("Audita embedded prompts", promptFS, prompts.AssetRoot)
|
||||||
options := []pk.Option{
|
|
||||||
pk.WithPromptFS(prompts.Filesystem(), prompts.AssetRoot),
|
|
||||||
pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot),
|
|
||||||
pk.WithProfileFS(profileFS, "."),
|
|
||||||
}
|
|
||||||
ids := make([]string, 0, len(c.Backends))
|
|
||||||
for id := range c.Backends {
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
sort.Strings(ids)
|
|
||||||
for _, id := range ids {
|
|
||||||
backend := c.Backends[id]
|
|
||||||
options = append(options, pk.WithBackend(pk.Backend{ID: id, Endpoint: backend.Endpoint, ConcurrencyLimit: backend.Concurrency, QueueCapacity: &backend.QueueCapacity}))
|
|
||||||
}
|
|
||||||
engine, err := pk.NewEngine(pk.Config{HTTPClient: client, TransientRetries: &c.LLM.TransientRetries}, options...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("assemble LLM runtime: %w", err)
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
|
return nil, ctxErr
|
||||||
|
}
|
||||||
|
return nil, &AssetError{Err: fmt.Errorf("discover embedded prompts: %w", err)}
|
||||||
|
}
|
||||||
|
profileInputs, err := pk.DirectoryInputs(profileDir)
|
||||||
|
if err != nil {
|
||||||
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
|
return nil, ctxErr
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("discover operator definitions: %w", err)
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
applicationBackends := make(map[string]any, len(c.Backends))
|
||||||
|
for id := range c.Backends {
|
||||||
|
backend := c.Backends[id]
|
||||||
|
applicationBackends[id] = map[string]any{
|
||||||
|
"endpoint": backend.Endpoint,
|
||||||
|
"concurrency_limit": backend.Concurrency,
|
||||||
|
"queue_capacity": backend.QueueCapacity,
|
||||||
|
"api_key_env": nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sources := pk.ConfigurationSources{
|
||||||
|
Underlays: []ck.Underlay{
|
||||||
|
{Inputs: promptInputs},
|
||||||
|
{Inputs: profileInputs},
|
||||||
|
},
|
||||||
|
Main: &ck.Input{Source: ck.Mapping("application backends", map[string]any{"backends": applicationBackends})},
|
||||||
|
}
|
||||||
|
engine, err := pk.NewEngine(pk.Config{
|
||||||
|
Sources: sources,
|
||||||
|
HTTPClient: client,
|
||||||
|
TransientRetries: &c.LLM.TransientRetries,
|
||||||
|
}, pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
|
||||||
|
if err != nil {
|
||||||
|
return nil, embeddedAssetOrInputError(ctx, promptInputs, fmt.Errorf("assemble LLM runtime: %w", err))
|
||||||
}
|
}
|
||||||
for _, meta := range prompts.RegisteredMetadata() {
|
for _, meta := range prompts.RegisteredMetadata() {
|
||||||
if _, err := engine.InspectPrompt(ctx, meta.PromptID, meta.PromptVersion); err != nil {
|
if _, err := engine.InspectPrompt(ctx, meta.PromptID, meta.PromptVersion); err != nil {
|
||||||
return nil, &AssetError{Err: fmt.Errorf("inspect embedded prompt %s: %w", meta.PromptID, err)}
|
cause := fmt.Errorf("inspect prompt %s: %w", meta.PromptID, err)
|
||||||
|
return nil, embeddedAssetOrInputError(ctx, promptInputs, cause)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runtime := &Runtime{Config: c, Engine: engine, Profiles: make(map[string]pk.ProfileInspection, 2)}
|
runtime := &Runtime{Config: c, Engine: engine, Profiles: make(map[string]pk.ProfileInspection, 2)}
|
||||||
@@ -78,6 +107,12 @@ func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *h
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%s profile must select a registered application backend", selection.role)
|
return nil, fmt.Errorf("%s profile must select a registered application backend", selection.role)
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(target.Model) == "" {
|
||||||
|
return nil, fmt.Errorf("%s profile must select a nonblank model", selection.role)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(target.Endpoint) == "" {
|
||||||
|
return nil, fmt.Errorf("%s profile must resolve to a nonblank endpoint", selection.role)
|
||||||
|
}
|
||||||
endpoint := func(value string) string { return strings.TrimRight(strings.TrimSpace(value), "/") }
|
endpoint := func(value string) string { return strings.TrimRight(strings.TrimSpace(value), "/") }
|
||||||
if endpoint(target.Endpoint) != endpoint(backend.Endpoint) {
|
if endpoint(target.Endpoint) != endpoint(backend.Endpoint) {
|
||||||
return nil, fmt.Errorf("%s profile endpoint must match its registered backend", selection.role)
|
return nil, fmt.Errorf("%s profile endpoint must match its registered backend", selection.role)
|
||||||
@@ -90,15 +125,39 @@ func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *h
|
|||||||
}
|
}
|
||||||
runtime.Profiles[selection.role] = *profile
|
runtime.Profiles[selection.role] = *profile
|
||||||
}
|
}
|
||||||
profileFS.mu.Lock()
|
runtime.SourcePaths, err = capturedSourcePaths(engine)
|
||||||
for name := range profileFS.paths {
|
if err != nil {
|
||||||
runtime.ProfilePaths = append(runtime.ProfilePaths, filepath.Join(profileDir, filepath.FromSlash(name)))
|
return nil, fmt.Errorf("inspect captured LLM sources: %w", err)
|
||||||
}
|
}
|
||||||
profileFS.mu.Unlock()
|
|
||||||
sort.Strings(runtime.ProfilePaths)
|
|
||||||
return runtime, nil
|
return runtime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func embeddedAssetOrInputError(ctx context.Context, promptInputs []ck.Input, cause error) error {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
embeddedSources := pk.ConfigurationSources{
|
||||||
|
Underlays: []ck.Underlay{{Inputs: promptInputs}},
|
||||||
|
}
|
||||||
|
diagnosticEngine, err := pk.NewEngine(pk.Config{Sources: embeddedSources},
|
||||||
|
pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
|
||||||
|
if err == nil {
|
||||||
|
for _, meta := range prompts.RegisteredMetadata() {
|
||||||
|
if _, inspectErr := diagnosticEngine.InspectPrompt(ctx, meta.PromptID, meta.PromptVersion); inspectErr != nil {
|
||||||
|
err = fmt.Errorf("inspect embedded prompt %s: %w", meta.PromptID, inspectErr)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
|
return ctxErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return &AssetError{Err: fmt.Errorf("validate embedded prompt assets: %w", err)}
|
||||||
|
}
|
||||||
|
return cause
|
||||||
|
}
|
||||||
|
|
||||||
func IsAssetError(err error) bool { var target *AssetError; return errors.As(err, &target) }
|
func IsAssetError(err error) bool { var target *AssetError; return errors.As(err, &target) }
|
||||||
|
|
||||||
// RedactError replaces every configured resolved backend secret before a
|
// RedactError replaces every configured resolved backend secret before a
|
||||||
@@ -109,21 +168,3 @@ func RedactError(err error, c config.Config) string {
|
|||||||
}
|
}
|
||||||
return redaction.New(ConfiguredSecrets(&c)).String(err.Error())
|
return redaction.New(ConfiguredSecrets(&c)).String(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record successful profile reads at the library boundary, including inherited
|
|
||||||
// and differently named sources, without duplicating PromptKit's discovery rules.
|
|
||||||
type profileSourceFS struct {
|
|
||||||
fs.FS
|
|
||||||
mu sync.Mutex
|
|
||||||
paths map[string]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *profileSourceFS) ReadFile(name string) ([]byte, error) {
|
|
||||||
data, err := fs.ReadFile(f.FS, name)
|
|
||||||
if err == nil {
|
|
||||||
f.mu.Lock()
|
|
||||||
f.paths[name] = struct{}{}
|
|
||||||
f.mu.Unlock()
|
|
||||||
}
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|||||||
257
internal/framework/llm/runtime_assembly_test.go
Normal file
257
internal/framework/llm/runtime_assembly_test.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
package llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runtimeAssemblyFixture(t *testing.T, selections string, documents map[string]string) (config.Config, string) {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
profileDir := filepath.Join(dir, "profiles")
|
||||||
|
if err := os.Mkdir(profileDir, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
main := filepath.Join(dir, "config.yml")
|
||||||
|
text := "version: 2\nprofiles_dir: profiles\nbackends:\n offline:\n endpoint: http://127.0.0.1:1/v1\n concurrency: 1\n queue_capacity: 0\nllm:\n" + selections
|
||||||
|
if err := os.WriteFile(main, []byte(text), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, content := range documents {
|
||||||
|
if err := os.WriteFile(filepath.Join(profileDir, name), []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loaded, err := config.Load(ck.File(main), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return loaded.Config, main
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyEmbeddedPromptFilesystem(t *testing.T) fstest.MapFS {
|
||||||
|
t.Helper()
|
||||||
|
source := prompts.Filesystem()
|
||||||
|
files := make(fstest.MapFS)
|
||||||
|
err := fs.WalkDir(source, prompts.AssetRoot, func(name string, entry fs.DirEntry, err error) error {
|
||||||
|
if err != nil || entry.IsDir() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := fs.ReadFile(source, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
files[name] = &fstest.MapFile{Data: data}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRuntimeRequiresCompleteSelectedProfiles(t *testing.T) {
|
||||||
|
validProposal := " backend: offline\n model: proposal-model\n"
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
selections string
|
||||||
|
profiles string
|
||||||
|
role string
|
||||||
|
field string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "omitted proposal model with derived validation selection",
|
||||||
|
selections: " proposal_profile: proposal\n",
|
||||||
|
profiles: "profiles:\n proposal:\n backend: offline\n",
|
||||||
|
role: "proposal",
|
||||||
|
field: "model",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty validation model",
|
||||||
|
selections: " proposal_profile: proposal\n validation_profile: validation\n",
|
||||||
|
profiles: "profiles:\n proposal:\n" + validProposal + " validation:\n backend: offline\n model: \"\"\n",
|
||||||
|
role: "validation",
|
||||||
|
field: "model",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "null proposal model",
|
||||||
|
selections: " proposal_profile: proposal\n",
|
||||||
|
profiles: "profiles:\n proposal:\n backend: offline\n model: null\n",
|
||||||
|
role: "proposal",
|
||||||
|
field: "model",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cleared validation endpoint",
|
||||||
|
selections: " proposal_profile: proposal\n validation_profile: validation\n",
|
||||||
|
profiles: "profiles:\n proposal:\n" + validProposal + " validation:\n backend: offline\n endpoint: null\n model: validation-model\n",
|
||||||
|
role: "validation",
|
||||||
|
field: "endpoint",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, tc.selections, map[string]string{"definitions.yml": tc.profiles})
|
||||||
|
_, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{t}})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tc.role) || !strings.Contains(err.Error(), tc.field) {
|
||||||
|
t.Fatalf("selected incomplete profile was not rejected for %s.%s: %v", tc.role, tc.field, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRuntimeAllowsUnselectedPartialProfiles(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n", map[string]string{
|
||||||
|
"definitions.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\n unfinished:\n backend: offline\n",
|
||||||
|
})
|
||||||
|
runtime, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{t}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if runtime.Profiles["proposal"].EffectiveModelParams.Model != "proposal-model" || runtime.Profiles["validation"].EffectiveModelParams.Model != "proposal-model" {
|
||||||
|
t.Fatalf("role profiles were not resolved: %+v", runtime.Profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeProfileInheritancePreservesControlsAndMergesProviderValues(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: \"child profile\"\n", map[string]string{
|
||||||
|
"unrelated-name.yaml": `profiles:
|
||||||
|
"parent~/shared":
|
||||||
|
backend: offline
|
||||||
|
temperature: 0.4
|
||||||
|
top_p: 0.7
|
||||||
|
extra_params:
|
||||||
|
routing:
|
||||||
|
retained: from-parent
|
||||||
|
overridden: parent
|
||||||
|
cleared: remove-me
|
||||||
|
"child profile":
|
||||||
|
extends: "parent~/shared"
|
||||||
|
model: child-model
|
||||||
|
extra_params:
|
||||||
|
routing:
|
||||||
|
overridden: child
|
||||||
|
child_only: present
|
||||||
|
cleared: null
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
runtime, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
profile := runtime.Profiles["proposal"]
|
||||||
|
target := profile.EffectiveModelParams
|
||||||
|
if profile.ProfileID != "child profile" || target.Model != "child-model" || target.Temperature != 0.4 || target.TopP != 0.7 {
|
||||||
|
t.Fatalf("inherited profile target = %+v", target)
|
||||||
|
}
|
||||||
|
routing, ok := target.ExtraParams["routing"].(map[string]any)
|
||||||
|
if !ok || routing["retained"] != "from-parent" || routing["overridden"] != "child" || routing["child_only"] != "present" {
|
||||||
|
t.Fatalf("recursively inherited provider values = %#v", target.ExtraParams)
|
||||||
|
}
|
||||||
|
if value, exists := routing["cleared"]; !exists || value != nil {
|
||||||
|
t.Fatalf("null did not replace the inherited provider value: %#v", routing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingSelectedProfileDirectoryIsOperatorInputError(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n", map[string]string{
|
||||||
|
"definitions.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\n",
|
||||||
|
})
|
||||||
|
if err := os.RemoveAll(filepath.Join(filepath.Dir(main), cfg.ProfilesDir)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{t}})
|
||||||
|
if err == nil || IsAssetError(err) {
|
||||||
|
t.Fatalf("missing operator profile directory classification: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRuntimeRejectsMalformedUnselectedProfilesAndConflicts(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
document map[string]string
|
||||||
|
selection string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "malformed unselected definition",
|
||||||
|
document: map[string]string{"definitions.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\n unused:\n temperature: 9\n"},
|
||||||
|
selection: " proposal_profile: proposal\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "undefined selected backend",
|
||||||
|
document: map[string]string{"definitions.yml": "profiles:\n proposal:\n backend: missing\n model: proposal-model\n"},
|
||||||
|
selection: " proposal_profile: proposal\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conflicting directory contributions",
|
||||||
|
document: map[string]string{
|
||||||
|
"a.yml": "profiles:\n proposal:\n backend: offline\n model: first-model\n",
|
||||||
|
"b.yml": "profiles:\n proposal:\n model: second-model\n",
|
||||||
|
},
|
||||||
|
selection: " proposal_profile: proposal\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, tc.selection, tc.document)
|
||||||
|
_, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{t}})
|
||||||
|
if err == nil || IsAssetError(err) {
|
||||||
|
t.Fatalf("operator definition classification: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRuntimeClassifiesEmbeddedAssetsAndHonorsCancellation(t *testing.T) {
|
||||||
|
_, err := newRuntimeWithPromptFS(context.Background(), config.Config{}, "config.yml", nil, fstest.MapFS{})
|
||||||
|
if err == nil || !IsAssetError(err) || !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
t.Fatalf("embedded discovery error was not preserved as an asset error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
_, err = newRuntimeWithPromptFS(ctx, config.Config{}, "config.yml", nil, fstest.MapFS{})
|
||||||
|
if !errors.Is(err, context.Canceled) || IsAssetError(err) {
|
||||||
|
t.Fatalf("caller cancellation classification: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenEmbeddedPromptFailsEmbeddedOnlyConstruction(t *testing.T) {
|
||||||
|
promptFS := copyEmbeddedPromptFilesystem(t)
|
||||||
|
assetPath := path.Join(prompts.AssetRoot, prompts.PromptIDModuleGrammarProposal+".yaml")
|
||||||
|
promptFS[assetPath] = &fstest.MapFile{Data: []byte("prompts:\n modules.grammar.proposal:\n v1:\n messages: []\n output:\n format: json\n validation_mode: json_schema\n schema_path: correction_set.v1.json\n")}
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n", map[string]string{
|
||||||
|
"definitions.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\n",
|
||||||
|
})
|
||||||
|
_, err := newRuntimeWithPromptFS(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{t}}, promptFS)
|
||||||
|
if err == nil || !IsAssetError(err) {
|
||||||
|
t.Fatalf("broken embedded prompt was not classified as an asset error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOperatorPromptCanRepairInvalidEmbeddedPrompt(t *testing.T) {
|
||||||
|
promptFS := copyEmbeddedPromptFilesystem(t)
|
||||||
|
assetPath := path.Join(prompts.AssetRoot, prompts.PromptIDModuleGrammarProposal+".yaml")
|
||||||
|
promptFS[assetPath] = &fstest.MapFile{Data: []byte("prompts:\n modules.grammar.proposal:\n v1:\n messages: []\n output:\n format: json\n validation_mode: json_schema\n schema_path: correction_set.v1.json\n")}
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n", map[string]string{
|
||||||
|
"operator.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\nprompts:\n modules.grammar.proposal:\n v1:\n messages:\n - role: system\n content: operator system\n - role: user\n content: operator user\n",
|
||||||
|
})
|
||||||
|
runtime, err := newRuntimeWithPromptFS(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{t}}, promptFS)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("valid final configuration was rejected with an invalid displaced embedded prompt: %v", err)
|
||||||
|
}
|
||||||
|
if runtime.Engine == nil {
|
||||||
|
t.Fatal("runtime engine was not retained")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rejectingTransport struct{ t *testing.T }
|
type rejectingTransport struct{ t *testing.T }
|
||||||
@@ -29,8 +30,8 @@ func profileLayout(t *testing.T, base, overlay string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for name, content := range map[string]string{
|
for name, content := range map[string]string{
|
||||||
"base": "id: base\nbackend: offline\nmodel: full-model\ntemperature: 0.3\nmax_tokens: 500\ntop_p: 0.8\ntimeout_seconds: 12\nservice_tier: priority\nreasoning_effort: medium\n",
|
"base": "profiles:\n base:\n backend: offline\n model: full-model\n temperature: 0.3\n max_tokens: 500\n top_p: 0.8\n timeout_seconds: 12\n service_tier: priority\n reasoning_effort: medium\n",
|
||||||
"testing": "id: testing\nbackend: offline\nmodel: economy-model\n",
|
"testing": "profiles:\n testing:\n backend: offline\n model: economy-model\n",
|
||||||
} {
|
} {
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", name+".yaml"), []byte(content), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", name+".yaml"), []byte(content), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -112,14 +113,111 @@ func TestOfflineProfileAssemblyAndSafeViews(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSafeViewsOmitProviderAndPromptValues(t *testing.T) {
|
||||||
|
const providerValue = "private-provider-value"
|
||||||
|
const promptValue = "private-operator-prompt-text"
|
||||||
|
runtime, _, dir := adapterFixtureWithDocuments(t, nil, 0, 0, nil, "offline", map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n extra_params:\n vendor_extension: " + providerValue + "\n",
|
||||||
|
"operator-prompt.yaml": operatorPromptDocument(promptValue, promptValue),
|
||||||
|
})
|
||||||
|
profiles, err := runtime.SelectedProfilesYAML()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loaded, err := config.Load(ck.File(filepath.Join(dir, "config.yml")), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
explain, err := ExplanationYAML(loaded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
combined := string(profiles) + string(explain)
|
||||||
|
for _, value := range []string{providerValue, promptValue, "adapter-secret", "127.0.0.1"} {
|
||||||
|
if strings.Contains(combined, value) {
|
||||||
|
t.Fatalf("safe views exposed %q: %s", value, combined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExplanationYAMLIncludesSafeInheritanceEvidence(t *testing.T) {
|
||||||
|
t.Setenv("SYNTHETIC_KEY", "synthetic-inherited-secret-never-print")
|
||||||
|
text := `version: 2
|
||||||
|
backends:
|
||||||
|
shared:
|
||||||
|
endpoint: https://shared.invalid/v1
|
||||||
|
concurrency: 3
|
||||||
|
queue_capacity: 0
|
||||||
|
credential: SYNTHETIC_KEY
|
||||||
|
offline:
|
||||||
|
extends: shared
|
||||||
|
concurrency: 1
|
||||||
|
llm:
|
||||||
|
proposal_profile: base
|
||||||
|
`
|
||||||
|
loaded, err := config.Load(ck.Bytes("config.yml", []byte(text)), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
explanation, err := ExplanationYAML(loaded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(explanation), "synthetic-inherited-secret-never-print") {
|
||||||
|
t.Fatal("explanation exposed a resolved inherited credential")
|
||||||
|
}
|
||||||
|
var records []struct {
|
||||||
|
Path string `yaml:"path"`
|
||||||
|
Inheritance []struct {
|
||||||
|
From string `yaml:"from"`
|
||||||
|
To string `yaml:"to"`
|
||||||
|
Reference string `yaml:"reference"`
|
||||||
|
} `yaml:"inheritance"`
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(explanation, &records); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var endpoint, credential, concurrency *struct {
|
||||||
|
Path string `yaml:"path"`
|
||||||
|
Inheritance []struct {
|
||||||
|
From string `yaml:"from"`
|
||||||
|
To string `yaml:"to"`
|
||||||
|
Reference string `yaml:"reference"`
|
||||||
|
} `yaml:"inheritance"`
|
||||||
|
}
|
||||||
|
for i := range records {
|
||||||
|
switch records[i].Path {
|
||||||
|
case "/backends/offline/endpoint":
|
||||||
|
endpoint = &records[i]
|
||||||
|
case "/backends/offline/credential":
|
||||||
|
credential = &records[i]
|
||||||
|
case "/backends/offline/concurrency":
|
||||||
|
concurrency = &records[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endpoint == nil || len(endpoint.Inheritance) != 1 {
|
||||||
|
t.Fatalf("inherited endpoint evidence missing: %s", explanation)
|
||||||
|
}
|
||||||
|
step := endpoint.Inheritance[0]
|
||||||
|
if step.From != "/backends/shared/endpoint" || step.To != "/backends/offline/endpoint" || step.Reference != "/backends/offline/extends" {
|
||||||
|
t.Fatalf("inherited endpoint evidence: %+v", step)
|
||||||
|
}
|
||||||
|
if credential == nil || len(credential.Inheritance) != 1 {
|
||||||
|
t.Fatalf("inherited credential evidence missing: %s", explanation)
|
||||||
|
}
|
||||||
|
if concurrency == nil || len(concurrency.Inheritance) != 0 {
|
||||||
|
t.Fatalf("direct child override reported parent inheritance: %+v", concurrency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProfileAssemblyRejectsUnsafeSelections(t *testing.T) {
|
func TestProfileAssemblyRejectsUnsafeSelections(t *testing.T) {
|
||||||
t.Setenv("SYNTHETIC_KEY", "synthetic-value-never-print")
|
t.Setenv("SYNTHETIC_KEY", "synthetic-value-never-print")
|
||||||
dir := profileLayout(t, profileBase, "")
|
dir := profileLayout(t, profileBase, "")
|
||||||
main := filepath.Join(dir, "config.yml")
|
main := filepath.Join(dir, "config.yml")
|
||||||
for _, tc := range []struct{ name, profile string }{
|
for _, tc := range []struct{ name, profile string }{
|
||||||
{"endpoint only", "id: custom\nendpoint: https://offline.invalid/v1\nmodel: model\n"},
|
{"endpoint only", "profiles:\n custom:\n endpoint: https://offline.invalid/v1\n model: model\n"},
|
||||||
{"mismatched endpoint", "id: custom\nbackend: offline\nendpoint: https://elsewhere.invalid/v1\nmodel: model\n"},
|
{"mismatched endpoint", "profiles:\n custom:\n backend: offline\n endpoint: https://elsewhere.invalid/v1\n model: model\n"},
|
||||||
{"profile credential", "id: custom\nbackend: offline\nmodel: model\napi_key_env: OTHER_KEY\n"},
|
{"profile credential", "profiles:\n custom:\n backend: offline\n model: model\n api_key_env: OTHER_KEY\n"},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "custom.yaml"), []byte(tc.profile), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "custom.yaml"), []byte(tc.profile), 0600); err != nil {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,11 +61,17 @@ type derivationView struct {
|
|||||||
Reason string `yaml:"reason"`
|
Reason string `yaml:"reason"`
|
||||||
Dependencies []string `yaml:"dependencies,omitempty"`
|
Dependencies []string `yaml:"dependencies,omitempty"`
|
||||||
}
|
}
|
||||||
|
type inheritanceStepView struct {
|
||||||
|
From string `yaml:"from"`
|
||||||
|
To string `yaml:"to"`
|
||||||
|
Reference string `yaml:"reference"`
|
||||||
|
}
|
||||||
type explanationView struct {
|
type explanationView struct {
|
||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
Source string `yaml:"source"`
|
Source string `yaml:"source"`
|
||||||
Layer ck.Layer `yaml:"layer"`
|
Layer ck.Layer `yaml:"layer"`
|
||||||
Derivations []derivationView `yaml:"derivations,omitempty"`
|
Derivations []derivationView `yaml:"derivations,omitempty"`
|
||||||
|
Inheritance []inheritanceStepView `yaml:"inheritance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExplanationYAML reports only origins and recorded resolution operations.
|
// ExplanationYAML reports only origins and recorded resolution operations.
|
||||||
@@ -93,6 +99,11 @@ func ExplanationYAML(result *ck.Result[config.Config]) ([]byte, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := explanationView{Path: path.String(), Layer: info.Origin.Layer, Source: string(info.Origin.Layer)}
|
record := explanationView{Path: path.String(), Layer: info.Origin.Layer, Source: string(info.Origin.Layer)}
|
||||||
|
for _, step := range info.Origin.Inheritance {
|
||||||
|
record.Inheritance = append(record.Inheritance, inheritanceStepView{
|
||||||
|
From: step.From.String(), To: step.To.String(), Reference: step.Reference.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
if len(info.Origin.Locations) > 0 {
|
if len(info.Origin.Locations) > 0 {
|
||||||
if label := sources[info.Origin.Locations[0].SourceID]; label != "" {
|
if label := sources[info.Origin.Locations[0].SourceID]; label != "" {
|
||||||
record.Source = label
|
record.Source = label
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package llm
|
package llm
|
||||||
|
|
||||||
import "gitea.maximumdirect.net/eric/audita/internal/core/config"
|
import "gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
|
|
||||||
// ConfiguredSecrets returns all configured LLM API-key values that should be
|
// ConfiguredSecrets returns all configured LLM API-key values that should be
|
||||||
// redacted from diagnostics and surfaced error payloads.
|
// redacted from diagnostics and surfaced error payloads.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfiguredSecretsIncludesUnselectedBackends(t *testing.T) {
|
func TestConfiguredSecretsIncludesUnselectedBackends(t *testing.T) {
|
||||||
|
|||||||
65
internal/framework/llm/source_paths.go
Normal file
65
internal/framework/llm/source_paths.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func capturedSourcePaths(engine *pk.Engine) ([]string, error) {
|
||||||
|
inspection, err := engine.InspectConfiguration()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("inspect configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sources := inspection.Sources()
|
||||||
|
byID := make(map[string]ck.SourceInfo, len(sources))
|
||||||
|
paths := make(map[string]struct{}, len(sources))
|
||||||
|
for _, source := range sources {
|
||||||
|
byID[source.ID] = source
|
||||||
|
if source.Kind == ck.SourceKindFile && source.Path != "" {
|
||||||
|
paths[source.Path] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := inspection.Metadata()
|
||||||
|
for _, path := range metadata.HistoricalPaths() {
|
||||||
|
segments := strings.Split(strings.TrimPrefix(path.String(), "/"), "/")
|
||||||
|
if len(segments) != 6 || segments[0] != "prompts" || segments[3] != "messages" || segments[5] != "content_file" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if messageIndex, err := strconv.Atoi(segments[4]); err != nil || messageIndex < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field, ok := metadata.Lookup(path)
|
||||||
|
if !ok || field.Null || len(field.Origin.Locations) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
source, ok := byID[field.Origin.Locations[0].SourceID]
|
||||||
|
if !ok || source.Kind != ck.SourceKindFile || source.Path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
explanation, ok := inspection.Explain(path)
|
||||||
|
if !ok || !explanation.Effective.Present || explanation.Effective.Redacted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var resource string
|
||||||
|
if err := yaml.Unmarshal([]byte(explanation.Effective.YAML), &resource); err != nil {
|
||||||
|
return nil, fmt.Errorf("decode captured message resource at %s: %w", path.String(), err)
|
||||||
|
}
|
||||||
|
paths[filepath.Join(filepath.Dir(source.Path), filepath.FromSlash(resource))] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, 0, len(paths))
|
||||||
|
for path := range paths {
|
||||||
|
result = append(result, path)
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
61
internal/framework/llm/source_paths_test.go
Normal file
61
internal/framework/llm/source_paths_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRuntimeTracksCapturedConfigurationAndInheritedMessageFiles(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n validation_profile: validation\n", map[string]string{
|
||||||
|
"selected.yml": "profiles:\n proposal:\n backend: offline\n model: proposal\n validation:\n backend: offline\n model: validation\n",
|
||||||
|
})
|
||||||
|
profiles := filepath.Join(filepath.Dir(main), cfg.ProfilesDir)
|
||||||
|
parentDir := filepath.Join(profiles, "catalog")
|
||||||
|
childDir := filepath.Join(profiles, "nested", "child")
|
||||||
|
for _, directory := range []string{parentDir, childDir} {
|
||||||
|
if err := os.MkdirAll(directory, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent := filepath.Join(parentDir, "arbitrary-name.yaml")
|
||||||
|
child := filepath.Join(childDir, "override.yaml")
|
||||||
|
resource := filepath.Join(parentDir, "instruction.md")
|
||||||
|
parentDocument := "prompts:\n source:\n release/1~draft:\n messages:\n - role: user\n content_file: ./instruction.md\n output:\n format: text\n validation_mode: basic\n"
|
||||||
|
childDocument := "prompts:\n literal/~child:\n extends: source\n"
|
||||||
|
for path, content := range map[string]string{
|
||||||
|
parent: parentDocument,
|
||||||
|
child: childDocument,
|
||||||
|
resource: "Captured operator message.\n",
|
||||||
|
} {
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := []string{
|
||||||
|
filepath.Join(profiles, "nested", "child", "override.yaml"),
|
||||||
|
filepath.Join(profiles, "catalog", "arbitrary-name.yaml"),
|
||||||
|
filepath.Join(profiles, "selected.yml"),
|
||||||
|
resource,
|
||||||
|
}
|
||||||
|
// Runtime paths are canonical paths from PromptKit's captured sources.
|
||||||
|
for i := range want {
|
||||||
|
want[i], err = filepath.EvalSymlinks(want[i])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(want)
|
||||||
|
if !reflect.DeepEqual(runtime.SourcePaths, want) {
|
||||||
|
t.Fatalf("captured source paths = %#v, want %#v", runtime.SourcePaths, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
||||||
glossarymodule "gitea.maximumdirect.net/eric/audita/internal/modules/glossary"
|
glossarymodule "gitea.maximumdirect.net/eric/audita/v3/internal/modules/glossary"
|
||||||
grammarmodule "gitea.maximumdirect.net/eric/audita/internal/modules/grammar"
|
grammarmodule "gitea.maximumdirect.net/eric/audita/v3/internal/modules/grammar"
|
||||||
homophonesmodule "gitea.maximumdirect.net/eric/audita/internal/modules/homophones"
|
homophonesmodule "gitea.maximumdirect.net/eric/audita/v3/internal/modules/homophones"
|
||||||
spokenwordmodule "gitea.maximumdirect.net/eric/audita/internal/modules/spoken_word"
|
spokenwordmodule "gitea.maximumdirect.net/eric/audita/v3/internal/modules/spoken_word"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
)
|
)
|
||||||
|
|
||||||
type noopModule struct {
|
type noopModule struct {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
||||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
validatormetadata "gitea.maximumdirect.net/eric/audita/v3/internal/validators/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildCorrectionLedgerClassifiesValidatorDecisionsFromCanonicalMetadata(t *testing.T) {
|
func TestBuildCorrectionLedgerClassifiesValidatorDecisionsFromCanonicalMetadata(t *testing.T) {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package processreport
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
pk "gitea.maximumdirect.net/eric/promptkit"
|
pk "gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/structuredoutput"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
type partialModule struct{ validator contracts.Validator }
|
type partialModule struct{ validator contracts.Validator }
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunFailure describes a fatal or output failure without carrying its raw cause.
|
// RunFailure describes a fatal or output failure without carrying its raw cause.
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/outcomes"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package promptcontext
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
type transcriptSectionSegment struct {
|
type transcriptSectionSegment struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalTranscriptSectionJSONShape(t *testing.T) {
|
func TestMarshalTranscriptSectionJSONShape(t *testing.T) {
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/prompts"
|
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRepairedProposalStillFacesDomainChecks(t *testing.T) {
|
func TestRepairedProposalStillFacesDomainChecks(t *testing.T) {
|
||||||
@@ -41,7 +41,7 @@ func TestRepairedProposalStillFacesDomainChecks(t *testing.T) {
|
|||||||
if err := os.WriteFile(main, []byte(content), 0600); err != nil {
|
if err := os.WriteFile(main, []byte(content), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "base.yaml"), []byte("id: base\nbackend: offline\nmodel: local-model\n"), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "base.yaml"), []byte("profiles:\n base:\n backend: offline\n model: local-model\n"), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
loaded, err := config.Load(ck.File(main), "")
|
loaded, err := config.Load(ck.File(main), "")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user