data-olympus 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {data_olympus-0.4.1 → data_olympus-0.4.2}/CHANGELOG.md +39 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/PKG-INFO +4 -1
- {data_olympus-0.4.1 → data_olympus-0.4.2}/README.md +3 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/pyproject.toml +1 -1
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/main.py +2 -0
- data_olympus-0.4.2/src/data_olympus/cli/verify_cmd.py +245 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/server.py +238 -64
- {data_olympus-0.4.1 → data_olympus-0.4.2}/.gitignore +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/LICENSE +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/NOTICE +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/SPEC.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/benchmarks/README.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/_kb_detect_workspace.sh +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/_kb_enforce.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/_kb_fallback.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/kb +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/kb-enforce-hook +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/kb-session-recap-hook +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/bin/opencode/data-olympus-gate.ts +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/deploy/k8s/README.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/deploy/k8s/read-replica/README.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/docs/releases/README.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/example-bundle/projects/acme-app/README.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/example-bundle/projects/acme-app/components/api/README.md +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/__init__.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/audit_log.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/audit_trailers.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/auth.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/__init__.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/import_cmd.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/indexgen.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/init_cmd.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/report_cmd.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cli/validity_report_cmd.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/config.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/cooccurrence.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/dedup.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/durable.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/embeddings.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/enforce_policy.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/format/__init__.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/format/document.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/format/frontmatter.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/format/lint.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/format/validate.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/git_ops.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/governed_lane.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/health.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/__init__.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/adr.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/flat.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/model.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/okf.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/run.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/importer/stamp.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/index.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/maintenance.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/markdown_parse.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/models.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/onboarding.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/onboarding_inflight.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/onboarding_playbook.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/pending.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/principals.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/prompts.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/push_queue.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/query_expansion.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/rate_limit.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/refresh.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/report.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/rest_api.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/safe_id.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/scaffold.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/search_gate.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/search_shortcut.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/session_metrics.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/setup_wizard.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/thin_pointer.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/tools_audit.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/tools_enforce.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/tools_onboarding.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/tools_read.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/tools_recap.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/tools_write.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/trigram.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/viewer/__init__.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/viewer/generator.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/worktrees.py +0 -0
- {data_olympus-0.4.1 → data_olympus-0.4.2}/src/data_olympus/write_gate.py +0 -0
|
@@ -12,6 +12,45 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.4.2] - 2026-07-11
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`data-olympus verify` command.** New CLI subcommand that runs pass/fail
|
|
20
|
+
checks against a running instance (`--target`, `--json`, `--checks`,
|
|
21
|
+
`--token`): liveness (`/api/v1/health`), readiness (`/readyz`), a search
|
|
22
|
+
round-trip (`/api/v1/search`), and a deployment-tolerant enforcement-plane
|
|
23
|
+
probe (`/api/v1/gate/check`). Exit codes: `0` all pass, `4` a check failed,
|
|
24
|
+
`1` the target is unreachable. Used as the pre/post-release verification gate.
|
|
25
|
+
- **Staged-promotion release pipeline (release engineering).** A release
|
|
26
|
+
candidate is published to a `X.Y.Z-rc.N` ghcr channel (`rc-publish.yml`),
|
|
27
|
+
deployed as a canary and verified, then promoted by re-tagging the verified
|
|
28
|
+
digest to `vX.Y.Z` byte-identically (`tag-release.yml`), with a moving-channel
|
|
29
|
+
helper (`set-channel.yml`) and project-local runbooks under `.rules/`.
|
|
30
|
+
- **Glama registry score support.** Added the Glama score badge to the README
|
|
31
|
+
and documented the claimed server listing, sandbox release settings, and
|
|
32
|
+
score-maintenance checklist in `docs/glama.md`.
|
|
33
|
+
- **Release security-clearance gate.** `scripts/security_alerts.py` fails a
|
|
34
|
+
release when any Dependabot or CodeQL alert is open; the release planner clears
|
|
35
|
+
every alert (fix or justified dismissal) and the cutter verifies zero open
|
|
36
|
+
before building the release candidate.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Improved MCP tool schema metadata for registry introspection.** MCP tools
|
|
41
|
+
now expose meaningful titles, MCP annotations, and per-parameter JSON Schema
|
|
42
|
+
descriptions so registries such as Glama can score the running server's tool
|
|
43
|
+
definitions more accurately.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **Documented the fastmcp 3.4.3 host allowlist requirement behind
|
|
48
|
+
proxies/ingress** (docs/serving.md, docs/operations.md section 3.4).
|
|
49
|
+
v0.4.1's fastmcp bump enables DNS-rebinding protection that answers 421
|
|
50
|
+
Misdirected Request for undeclared public hostnames while health probes
|
|
51
|
+
keep passing; deployments behind a reverse proxy must set
|
|
52
|
+
`FASTMCP_HTTP_ALLOWED_HOSTS`. First-class handling tracked in #139.
|
|
53
|
+
|
|
15
54
|
## [0.4.1] - 2026-07-08
|
|
16
55
|
|
|
17
56
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-olympus
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Governance-grade knowledge-base format (OKF-compatible) plus CLI and single-writer MCP server
|
|
5
5
|
Project-URL: Homepage, https://github.com/knaisoma/data-olympus
|
|
6
6
|
Project-URL: Repository, https://github.com/knaisoma/data-olympus
|
|
@@ -45,6 +45,8 @@ Description-Content-Type: text/markdown
|
|
|
45
45
|
|
|
46
46
|
# data-olympus
|
|
47
47
|
|
|
48
|
+
[](https://glama.ai/mcp/servers/knaisoma/data-olympus)
|
|
49
|
+
|
|
48
50
|
**New here? Start with [WHY.md](WHY.md).** It is the story behind the project: the
|
|
49
51
|
problem we kept hitting with coding agents, what data-olympus does differently, how
|
|
50
52
|
it relates to Google's Open Knowledge Format, and where our benchmarks say it is
|
|
@@ -94,6 +96,7 @@ See `docs/quickstart.md` for the full local-run walkthrough, including curl and
|
|
|
94
96
|
- [`docs/operations.md`](docs/operations.md): production runbook — backup, upgrade, recovery playbooks (degraded/fetch-failed, history rewrite, frozen/demoted push entries, orphaned locks), and the health/alerting model.
|
|
95
97
|
- [`docs/comparison.md`](docs/comparison.md): how data-olympus relates to OKF, enterprise catalogs, markdown KB tools, agent-context conventions, RAG, and ADR tooling.
|
|
96
98
|
- [`docs/okf-profile.md`](docs/okf-profile.md): field-by-field OKF profile — which governance extensions are stable, which are runtime-only serving fields, and which are experimental candidates.
|
|
99
|
+
- [`docs/glama.md`](docs/glama.md): Glama registry claim, release, and score-maintenance notes.
|
|
97
100
|
- [`docs/enforcement.md`](docs/enforcement.md): turning the KB into a mandatory consultation gate (hooks, `kb enforce`).
|
|
98
101
|
- [`benchmarks/README.md`](benchmarks/README.md): retrieval benchmark methodology and how to reproduce the numbers in `docs/comparison.md`.
|
|
99
102
|
- [`SECURITY.md`](SECURITY.md): supported versions and how to report a vulnerability.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# data-olympus
|
|
2
2
|
|
|
3
|
+
[](https://glama.ai/mcp/servers/knaisoma/data-olympus)
|
|
4
|
+
|
|
3
5
|
**New here? Start with [WHY.md](WHY.md).** It is the story behind the project: the
|
|
4
6
|
problem we kept hitting with coding agents, what data-olympus does differently, how
|
|
5
7
|
it relates to Google's Open Knowledge Format, and where our benchmarks say it is
|
|
@@ -49,6 +51,7 @@ See `docs/quickstart.md` for the full local-run walkthrough, including curl and
|
|
|
49
51
|
- [`docs/operations.md`](docs/operations.md): production runbook — backup, upgrade, recovery playbooks (degraded/fetch-failed, history rewrite, frozen/demoted push entries, orphaned locks), and the health/alerting model.
|
|
50
52
|
- [`docs/comparison.md`](docs/comparison.md): how data-olympus relates to OKF, enterprise catalogs, markdown KB tools, agent-context conventions, RAG, and ADR tooling.
|
|
51
53
|
- [`docs/okf-profile.md`](docs/okf-profile.md): field-by-field OKF profile — which governance extensions are stable, which are runtime-only serving fields, and which are experimental candidates.
|
|
54
|
+
- [`docs/glama.md`](docs/glama.md): Glama registry claim, release, and score-maintenance notes.
|
|
52
55
|
- [`docs/enforcement.md`](docs/enforcement.md): turning the KB into a mandatory consultation gate (hooks, `kb enforce`).
|
|
53
56
|
- [`benchmarks/README.md`](benchmarks/README.md): retrieval benchmark methodology and how to reproduce the numbers in `docs/comparison.md`.
|
|
54
57
|
- [`SECURITY.md`](SECURITY.md): supported versions and how to report a vulnerability.
|
|
@@ -141,6 +141,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
141
141
|
add_validity_report_subparser(sub)
|
|
142
142
|
from data_olympus.cli.init_cmd import add_init_subparser
|
|
143
143
|
add_init_subparser(sub)
|
|
144
|
+
from data_olympus.cli.verify_cmd import add_verify_subparser
|
|
145
|
+
add_verify_subparser(sub)
|
|
144
146
|
return parser
|
|
145
147
|
|
|
146
148
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""`data-olympus verify`: automated pass/fail health + functional round-trip
|
|
2
|
+
checks against a running data-olympus instance. Used by hand and as the
|
|
3
|
+
pre/post-release verification gate.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
# NOTE: declare every module-level import here in Task 1. Later tasks add
|
|
17
|
+
# functions only (no new top-level imports), so ruff's E402 never fires.
|
|
18
|
+
# `json` is used by run_verify (Task 4); the argparse annotations are strings.
|
|
19
|
+
|
|
20
|
+
_ALL_CHECKS = ("health", "readiness", "search", "enforcement")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class CheckResult:
|
|
25
|
+
name: str
|
|
26
|
+
ok: bool
|
|
27
|
+
detail: str
|
|
28
|
+
connection_error: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def check_health(client: httpx.Client) -> CheckResult:
|
|
32
|
+
"""GET /api/v1/health: pass on 200 with degraded=false (503 = degraded)."""
|
|
33
|
+
try:
|
|
34
|
+
resp = client.get("/api/v1/health")
|
|
35
|
+
except httpx.HTTPError as exc:
|
|
36
|
+
return CheckResult("health", False, f"request failed: {exc}", connection_error=True)
|
|
37
|
+
if resp.status_code != 200:
|
|
38
|
+
return CheckResult("health", False, f"status {resp.status_code} (degraded or down)")
|
|
39
|
+
try:
|
|
40
|
+
body = resp.json()
|
|
41
|
+
except ValueError:
|
|
42
|
+
return CheckResult("health", False, "non-JSON health body")
|
|
43
|
+
if not isinstance(body, dict):
|
|
44
|
+
return CheckResult("health", False, "unexpected non-object health body")
|
|
45
|
+
if bool(body.get("degraded")):
|
|
46
|
+
return CheckResult("health", False, "health reports degraded")
|
|
47
|
+
return CheckResult("health", True, "healthy")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def check_readiness(client: httpx.Client) -> CheckResult:
|
|
51
|
+
"""GET /readyz: the k8s readiness probe target; pass on 200."""
|
|
52
|
+
try:
|
|
53
|
+
resp = client.get("/readyz")
|
|
54
|
+
except httpx.HTTPError as exc:
|
|
55
|
+
return CheckResult("readiness", False, f"request failed: {exc}", connection_error=True)
|
|
56
|
+
ok = resp.status_code == 200
|
|
57
|
+
return CheckResult("readiness", ok, "ready" if ok else f"status {resp.status_code}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def check_search(client: httpx.Client, probe: str) -> CheckResult:
|
|
61
|
+
"""GET /api/v1/search: confirms the index answers reads with a hits list."""
|
|
62
|
+
try:
|
|
63
|
+
resp = client.get("/api/v1/search", params={"q": probe, "limit": 1})
|
|
64
|
+
except httpx.HTTPError as exc:
|
|
65
|
+
return CheckResult("search", False, f"request failed: {exc}", connection_error=True)
|
|
66
|
+
if resp.status_code != 200:
|
|
67
|
+
return CheckResult("search", False, f"status {resp.status_code}")
|
|
68
|
+
try:
|
|
69
|
+
body = resp.json()
|
|
70
|
+
except ValueError:
|
|
71
|
+
return CheckResult("search", False, "non-JSON search body")
|
|
72
|
+
if not isinstance(body, dict):
|
|
73
|
+
return CheckResult("search", False, "unexpected non-object search body")
|
|
74
|
+
hits = body.get("hits")
|
|
75
|
+
if not isinstance(hits, list):
|
|
76
|
+
return CheckResult("search", False, "response missing 'hits' list")
|
|
77
|
+
return CheckResult("search", True, f"{len(hits)} hit(s) for probe {probe!r}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def check_enforcement(client: httpx.Client, token: str | None = None) -> CheckResult:
|
|
81
|
+
"""Probe /api/v1/gate/check. Deployment-tolerant: 404 (routes not
|
|
82
|
+
mounted) and 401/403 without a token are informational passes; a token
|
|
83
|
+
that is rejected, or a 5xx, fail."""
|
|
84
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
85
|
+
payload = {"workspace": "data-olympus", "session_id": "verify-probe"}
|
|
86
|
+
try:
|
|
87
|
+
resp = client.post(
|
|
88
|
+
"/api/v1/gate/check", json=payload, headers=headers
|
|
89
|
+
)
|
|
90
|
+
except httpx.HTTPError as exc:
|
|
91
|
+
return CheckResult(
|
|
92
|
+
"enforcement", False, f"request failed: {exc}", connection_error=True
|
|
93
|
+
)
|
|
94
|
+
code = resp.status_code
|
|
95
|
+
if code == 200:
|
|
96
|
+
return CheckResult("enforcement", True, "enforcement plane responding")
|
|
97
|
+
if code == 404:
|
|
98
|
+
return CheckResult(
|
|
99
|
+
"enforcement", True, "enforcement routes not mounted (open deployment)"
|
|
100
|
+
)
|
|
101
|
+
if code in (401, 403):
|
|
102
|
+
if token:
|
|
103
|
+
return CheckResult(
|
|
104
|
+
"enforcement", False, f"enforcement rejected the token ({code})"
|
|
105
|
+
)
|
|
106
|
+
return CheckResult(
|
|
107
|
+
"enforcement",
|
|
108
|
+
True,
|
|
109
|
+
"enforcement active (auth required; pass --token to test round-trip)",
|
|
110
|
+
)
|
|
111
|
+
return CheckResult("enforcement", False, f"unexpected status {code}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def run_verify(
|
|
115
|
+
*,
|
|
116
|
+
target: str,
|
|
117
|
+
as_json: bool = False,
|
|
118
|
+
timeout: float = 10.0,
|
|
119
|
+
probe: str = "the",
|
|
120
|
+
checks: list[str] | None = None,
|
|
121
|
+
token: str | None = None,
|
|
122
|
+
client: httpx.Client | None = None,
|
|
123
|
+
) -> int:
|
|
124
|
+
"""Orchestrate selected checks and report results to stdout. Returns 0, 1,
|
|
125
|
+
or 4."""
|
|
126
|
+
owns_client = client is None
|
|
127
|
+
if client is None:
|
|
128
|
+
client = httpx.Client(base_url=target.rstrip("/"), timeout=timeout)
|
|
129
|
+
try:
|
|
130
|
+
selected_checks = (
|
|
131
|
+
checks if checks is not None else list(_ALL_CHECKS)
|
|
132
|
+
)
|
|
133
|
+
results = []
|
|
134
|
+
for check_name in _ALL_CHECKS:
|
|
135
|
+
if check_name not in selected_checks:
|
|
136
|
+
continue
|
|
137
|
+
if check_name == "health":
|
|
138
|
+
results.append(check_health(client))
|
|
139
|
+
elif check_name == "readiness":
|
|
140
|
+
results.append(check_readiness(client))
|
|
141
|
+
elif check_name == "search":
|
|
142
|
+
results.append(check_search(client, probe))
|
|
143
|
+
elif check_name == "enforcement":
|
|
144
|
+
results.append(check_enforcement(client, token))
|
|
145
|
+
finally:
|
|
146
|
+
if owns_client:
|
|
147
|
+
client.close()
|
|
148
|
+
|
|
149
|
+
all_ok = all(r.ok for r in results)
|
|
150
|
+
if as_json:
|
|
151
|
+
print(
|
|
152
|
+
json.dumps(
|
|
153
|
+
{
|
|
154
|
+
"target": target,
|
|
155
|
+
"ok": all_ok,
|
|
156
|
+
"checks": [
|
|
157
|
+
{"name": r.name, "ok": r.ok, "detail": r.detail}
|
|
158
|
+
for r in results
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
indent=2,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
for r in results:
|
|
166
|
+
mark = "PASS" if r.ok else "FAIL"
|
|
167
|
+
print(f"{mark} {r.name}: {r.detail}")
|
|
168
|
+
print(
|
|
169
|
+
f"{'ok' if all_ok else 'FAILED'}: "
|
|
170
|
+
f"{sum(r.ok for r in results)}/{len(results)} checks passed "
|
|
171
|
+
f"against {target}"
|
|
172
|
+
)
|
|
173
|
+
if all_ok:
|
|
174
|
+
return 0
|
|
175
|
+
if results and all(r.connection_error for r in results):
|
|
176
|
+
return 1
|
|
177
|
+
return 4
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def add_verify_subparser(
|
|
181
|
+
sub: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Wire the verify subcommand into the argparse dispatcher."""
|
|
184
|
+
p = sub.add_parser(
|
|
185
|
+
"verify",
|
|
186
|
+
help="run health + functional round-trip checks against a running instance",
|
|
187
|
+
)
|
|
188
|
+
p.add_argument(
|
|
189
|
+
"--target",
|
|
190
|
+
default=None,
|
|
191
|
+
help="base URL (default: $KB_ENDPOINT or http://localhost:8080)",
|
|
192
|
+
)
|
|
193
|
+
p.add_argument("--json", action="store_true", help="emit JSON")
|
|
194
|
+
p.add_argument(
|
|
195
|
+
"--timeout",
|
|
196
|
+
type=float,
|
|
197
|
+
default=10.0,
|
|
198
|
+
help="per-request timeout seconds",
|
|
199
|
+
)
|
|
200
|
+
p.add_argument("--probe", default="the", help="search probe term (default: the)")
|
|
201
|
+
p.add_argument(
|
|
202
|
+
"--checks",
|
|
203
|
+
default=None,
|
|
204
|
+
help="comma-separated subset to run (default: all): "
|
|
205
|
+
"health,readiness,search,enforcement",
|
|
206
|
+
)
|
|
207
|
+
p.add_argument(
|
|
208
|
+
"--token",
|
|
209
|
+
default=None,
|
|
210
|
+
help="bearer token for enforcement check (default: $KB_AUTH_TOKEN)",
|
|
211
|
+
)
|
|
212
|
+
p.set_defaults(func=_cmd_verify)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _cmd_verify(args: argparse.Namespace) -> int:
|
|
216
|
+
"""Entry point for the verify subcommand; resolve target and call
|
|
217
|
+
run_verify."""
|
|
218
|
+
import os
|
|
219
|
+
import sys
|
|
220
|
+
|
|
221
|
+
target = args.target or os.environ.get("KB_ENDPOINT") or (
|
|
222
|
+
"http://localhost:8080"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
checks = None
|
|
226
|
+
if args.checks:
|
|
227
|
+
checks = [c.strip() for c in args.checks.split(",")]
|
|
228
|
+
unknown = set(checks) - set(_ALL_CHECKS)
|
|
229
|
+
if unknown:
|
|
230
|
+
print(
|
|
231
|
+
f"error: unknown check(s): {','.join(sorted(unknown))}",
|
|
232
|
+
file=sys.stderr,
|
|
233
|
+
)
|
|
234
|
+
return 2
|
|
235
|
+
|
|
236
|
+
token = args.token or os.environ.get("KB_AUTH_TOKEN")
|
|
237
|
+
|
|
238
|
+
return run_verify(
|
|
239
|
+
target=target,
|
|
240
|
+
as_json=args.json,
|
|
241
|
+
timeout=args.timeout,
|
|
242
|
+
probe=args.probe,
|
|
243
|
+
checks=checks,
|
|
244
|
+
token=token,
|
|
245
|
+
)
|
|
@@ -6,10 +6,11 @@ import contextlib
|
|
|
6
6
|
import contextvars
|
|
7
7
|
import logging
|
|
8
8
|
import time
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
9
|
+
from typing import TYPE_CHECKING, Annotated, Any
|
|
10
10
|
|
|
11
11
|
from fastmcp import FastMCP
|
|
12
12
|
from fastmcp.server.middleware import Middleware
|
|
13
|
+
from pydantic import Field
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
from collections.abc import Callable
|
|
@@ -61,6 +62,170 @@ _current_principal: contextvars.ContextVar[Principal] = contextvars.ContextVar(
|
|
|
61
62
|
"current_principal", default=LOCAL_TRUSTED
|
|
62
63
|
)
|
|
63
64
|
|
|
65
|
+
READ_ONLY_TOOL = {
|
|
66
|
+
"readOnlyHint": True,
|
|
67
|
+
"destructiveHint": False,
|
|
68
|
+
"idempotentHint": True,
|
|
69
|
+
"openWorldHint": False,
|
|
70
|
+
}
|
|
71
|
+
STATEFUL_NONDESTRUCTIVE_TOOL = {
|
|
72
|
+
"readOnlyHint": False,
|
|
73
|
+
"destructiveHint": False,
|
|
74
|
+
"idempotentHint": False,
|
|
75
|
+
"openWorldHint": False,
|
|
76
|
+
}
|
|
77
|
+
PROPOSAL_WRITE_TOOL = {
|
|
78
|
+
"readOnlyHint": False,
|
|
79
|
+
"destructiveHint": False,
|
|
80
|
+
"idempotentHint": False,
|
|
81
|
+
"openWorldHint": True,
|
|
82
|
+
}
|
|
83
|
+
PROPOSAL_EDIT_TOOL = {
|
|
84
|
+
"readOnlyHint": False,
|
|
85
|
+
"destructiveHint": True,
|
|
86
|
+
"idempotentHint": False,
|
|
87
|
+
"openWorldHint": True,
|
|
88
|
+
}
|
|
89
|
+
DESTRUCTIVE_WRITE_TOOL = {
|
|
90
|
+
"readOnlyHint": False,
|
|
91
|
+
"destructiveHint": True,
|
|
92
|
+
"idempotentHint": False,
|
|
93
|
+
"openWorldHint": True,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
VerboseParam = Annotated[
|
|
97
|
+
bool,
|
|
98
|
+
Field(description="False returns the compact response; true includes all fields."),
|
|
99
|
+
]
|
|
100
|
+
QueryParam = Annotated[str, Field(description="Natural-language search query.")]
|
|
101
|
+
LimitParam = Annotated[int, Field(description="Maximum number of results, clamped to 1..100.")]
|
|
102
|
+
TierParam = Annotated[
|
|
103
|
+
str | None,
|
|
104
|
+
Field(description="Optional tier filter such as T1, T2, T3, or T4."),
|
|
105
|
+
]
|
|
106
|
+
RequiredTierParam = Annotated[
|
|
107
|
+
str,
|
|
108
|
+
Field(description="Required tier such as T1, T2, T3, or T4."),
|
|
109
|
+
]
|
|
110
|
+
CategoryParam = Annotated[
|
|
111
|
+
str | None,
|
|
112
|
+
Field(description="Optional category filter within a tier."),
|
|
113
|
+
]
|
|
114
|
+
StatusParam = Annotated[str | None, Field(description="Optional frontmatter status filter.")]
|
|
115
|
+
InForceParam = Annotated[
|
|
116
|
+
bool,
|
|
117
|
+
Field(description="True returns only currently governing documents."),
|
|
118
|
+
]
|
|
119
|
+
DocTypeParam = Annotated[str | None, Field(description="Optional document type filter.")]
|
|
120
|
+
AbstainParam = Annotated[
|
|
121
|
+
bool,
|
|
122
|
+
Field(description="True returns an explicit abstention when the query has no KB signal."),
|
|
123
|
+
]
|
|
124
|
+
IncludeExpiredParam = Annotated[
|
|
125
|
+
bool,
|
|
126
|
+
Field(description="True allows expired documents in search results."),
|
|
127
|
+
]
|
|
128
|
+
ValidityStateParam = Annotated[
|
|
129
|
+
str | None,
|
|
130
|
+
Field(description="Optional validity facet: expired, stale, or expiring_within:N."),
|
|
131
|
+
]
|
|
132
|
+
DocumentIdParam = Annotated[str, Field(description="Stable KB document id to retrieve.")]
|
|
133
|
+
WorkspaceParam = Annotated[str, Field(description="Project or workspace key in the KB.")]
|
|
134
|
+
ComponentParam = Annotated[
|
|
135
|
+
str | None,
|
|
136
|
+
Field(description="Optional component key under the workspace."),
|
|
137
|
+
]
|
|
138
|
+
RemoteUrlParam = Annotated[
|
|
139
|
+
str | None,
|
|
140
|
+
Field(description="Optional git remote URL used for matching."),
|
|
141
|
+
]
|
|
142
|
+
LocalFilesParam = Annotated[
|
|
143
|
+
list[dict[str, str]],
|
|
144
|
+
Field(description="Local docs to compare, each with path and content keys."),
|
|
145
|
+
]
|
|
146
|
+
JaccardParam = Annotated[
|
|
147
|
+
float,
|
|
148
|
+
Field(description="Similarity threshold above which a local doc is treated as duplicate."),
|
|
149
|
+
]
|
|
150
|
+
TextParam = Annotated[str, Field(description="Markdown memory text to propose.")]
|
|
151
|
+
TagsParam = Annotated[list[str], Field(description="Short tags for the proposed memory.")]
|
|
152
|
+
SourceSessionParam = Annotated[
|
|
153
|
+
str,
|
|
154
|
+
Field(description="Stable id of the agent session making the call."),
|
|
155
|
+
]
|
|
156
|
+
AgentIdentityParam = Annotated[
|
|
157
|
+
str,
|
|
158
|
+
Field(description="Human-readable agent identity for audit events."),
|
|
159
|
+
]
|
|
160
|
+
ConfidenceParam = Annotated[
|
|
161
|
+
float,
|
|
162
|
+
Field(description="Caller confidence in the proposal, from 0.0 to 1.0."),
|
|
163
|
+
]
|
|
164
|
+
EvidenceParam = Annotated[
|
|
165
|
+
list[str] | None,
|
|
166
|
+
Field(description="Optional supporting evidence strings, max 10 items of 500 chars each."),
|
|
167
|
+
]
|
|
168
|
+
TargetPathParam = Annotated[
|
|
169
|
+
str,
|
|
170
|
+
Field(description="KB-relative markdown path to create or edit."),
|
|
171
|
+
]
|
|
172
|
+
PostimageParam = Annotated[
|
|
173
|
+
str,
|
|
174
|
+
Field(description="Complete markdown file content after the proposed edit."),
|
|
175
|
+
]
|
|
176
|
+
BaseCommitParam = Annotated[str, Field(description="Git commit the proposal was based on.")]
|
|
177
|
+
BaseBlobShaParam = Annotated[
|
|
178
|
+
str | None,
|
|
179
|
+
Field(description="Optional git blob sha for compare-and-swap protection."),
|
|
180
|
+
]
|
|
181
|
+
TargetFileHashParam = Annotated[
|
|
182
|
+
str | None,
|
|
183
|
+
Field(description="Optional content hash for compare-and-swap protection."),
|
|
184
|
+
]
|
|
185
|
+
ReasonParam = Annotated[str, Field(description="Short reason for the proposed change.")]
|
|
186
|
+
PendingIdParam = Annotated[str, Field(description="Pending proposal id to resolve.")]
|
|
187
|
+
DecisionParam = Annotated[
|
|
188
|
+
str,
|
|
189
|
+
Field(description="Resolution decision: approve or reject."),
|
|
190
|
+
]
|
|
191
|
+
EditedTextParam = Annotated[
|
|
192
|
+
str | None,
|
|
193
|
+
Field(description="Optional replacement postimage used when approving."),
|
|
194
|
+
]
|
|
195
|
+
OverrideSecretScanParam = Annotated[
|
|
196
|
+
bool,
|
|
197
|
+
Field(description="Operator override for false-positive secret-scan matches."),
|
|
198
|
+
]
|
|
199
|
+
SinceParam = Annotated[float | None, Field(description="Optional Unix timestamp lower bound.")]
|
|
200
|
+
AgentParam = Annotated[str | None, Field(description="Optional agent_identity filter.")]
|
|
201
|
+
EventStatusParam = Annotated[str | None, Field(description="Optional audit event status filter.")]
|
|
202
|
+
AuditLimitParam = Annotated[int, Field(description="Maximum audit events to return.")]
|
|
203
|
+
BootstrapFilesParam = Annotated[
|
|
204
|
+
list[dict[str, str]],
|
|
205
|
+
Field(description="Bootstrap files, each with target_path and postimage keys."),
|
|
206
|
+
]
|
|
207
|
+
TriggerParam = Annotated[
|
|
208
|
+
str,
|
|
209
|
+
Field(description="Consult trigger: explicit or prompt_hook."),
|
|
210
|
+
]
|
|
211
|
+
SessionIdParam = Annotated[
|
|
212
|
+
str,
|
|
213
|
+
Field(description="Agent session id checked against consult history."),
|
|
214
|
+
]
|
|
215
|
+
ToolNameParam = Annotated[str, Field(description="Name of the tool or command about to run.")]
|
|
216
|
+
ActionPathParam = Annotated[
|
|
217
|
+
str | None,
|
|
218
|
+
Field(description="Optional path or URL affected by the pending action."),
|
|
219
|
+
]
|
|
220
|
+
ActionDiffParam = Annotated[
|
|
221
|
+
str,
|
|
222
|
+
Field(description="Short description or diff summary of the pending action."),
|
|
223
|
+
]
|
|
224
|
+
EventTypeParam = Annotated[
|
|
225
|
+
str,
|
|
226
|
+
Field(description="Client-reported event type: gate_bypass or gate_degraded."),
|
|
227
|
+
]
|
|
228
|
+
|
|
64
229
|
|
|
65
230
|
class MCPAuthMiddleware(Middleware):
|
|
66
231
|
"""Enforce principal capabilities on MCP write tools.
|
|
@@ -444,8 +609,8 @@ def build_app(
|
|
|
444
609
|
|
|
445
610
|
app: FastMCP = FastMCP(name="data-olympus-mcp")
|
|
446
611
|
|
|
447
|
-
@app.tool()
|
|
448
|
-
def kb_health(verbose:
|
|
612
|
+
@app.tool(title="KB Health", annotations=READ_ONLY_TOOL)
|
|
613
|
+
def kb_health(verbose: VerboseParam = False) -> dict[str, object]:
|
|
449
614
|
"""Return service health: kb_commit, index_built_at, staleness, degraded flag,
|
|
450
615
|
and write-side state (pending_count, push_queue_size, last_index_*).
|
|
451
616
|
|
|
@@ -480,8 +645,8 @@ def build_app(
|
|
|
480
645
|
)
|
|
481
646
|
return shape_response(resp, verbose=verbose)
|
|
482
647
|
|
|
483
|
-
@app.tool()
|
|
484
|
-
def kb_outline(verbose:
|
|
648
|
+
@app.tool(title="KB Outline", annotations=READ_ONLY_TOOL)
|
|
649
|
+
def kb_outline(verbose: VerboseParam = False) -> dict[str, object]:
|
|
485
650
|
"""Return the tree of tiers and categories with doc counts.
|
|
486
651
|
|
|
487
652
|
verbose: kb_outline is already lean, so compact and full modes return the
|
|
@@ -489,19 +654,19 @@ def build_app(
|
|
|
489
654
|
resp = kb_outline_fn(idx=state.idx)
|
|
490
655
|
return shape_response(resp, verbose=verbose)
|
|
491
656
|
|
|
492
|
-
@app.tool()
|
|
657
|
+
@app.tool(title="KB Search", annotations=READ_ONLY_TOOL)
|
|
493
658
|
def kb_search(
|
|
494
|
-
query:
|
|
495
|
-
limit:
|
|
496
|
-
tier:
|
|
497
|
-
category:
|
|
498
|
-
status:
|
|
499
|
-
in_force:
|
|
500
|
-
doc_type:
|
|
501
|
-
abstain:
|
|
502
|
-
include_expired:
|
|
503
|
-
validity_state:
|
|
504
|
-
verbose:
|
|
659
|
+
query: QueryParam,
|
|
660
|
+
limit: LimitParam = 20,
|
|
661
|
+
tier: TierParam = None,
|
|
662
|
+
category: CategoryParam = None,
|
|
663
|
+
status: StatusParam = None,
|
|
664
|
+
in_force: InForceParam = False,
|
|
665
|
+
doc_type: DocTypeParam = None,
|
|
666
|
+
abstain: AbstainParam = False,
|
|
667
|
+
include_expired: IncludeExpiredParam = False,
|
|
668
|
+
validity_state: ValidityStateParam = None,
|
|
669
|
+
verbose: VerboseParam = False,
|
|
505
670
|
) -> dict[str, object]:
|
|
506
671
|
"""Full-text search across the KB.
|
|
507
672
|
|
|
@@ -555,8 +720,8 @@ def build_app(
|
|
|
555
720
|
)
|
|
556
721
|
return shape_response(resp, verbose=verbose)
|
|
557
722
|
|
|
558
|
-
@app.tool()
|
|
559
|
-
def kb_get(id:
|
|
723
|
+
@app.tool(title="KB Get Document", annotations=READ_ONLY_TOOL)
|
|
724
|
+
def kb_get(id: DocumentIdParam, verbose: VerboseParam = False) -> dict[str, object]:
|
|
560
725
|
"""Retrieve a document by id (STD-U-001, ADR-002, T-NNN, etc.).
|
|
561
726
|
Returns the full content markdown plus metadata.
|
|
562
727
|
|
|
@@ -582,8 +747,11 @@ def build_app(
|
|
|
582
747
|
return {"error": "not_found", "message": str(e)}
|
|
583
748
|
return shape_response(resp, verbose=verbose)
|
|
584
749
|
|
|
585
|
-
@app.tool()
|
|
586
|
-
def kb_list(
|
|
750
|
+
@app.tool(title="KB List Documents", annotations=READ_ONLY_TOOL)
|
|
751
|
+
def kb_list(
|
|
752
|
+
tier: RequiredTierParam, category: CategoryParam = None,
|
|
753
|
+
verbose: VerboseParam = False,
|
|
754
|
+
) -> dict[str, object]:
|
|
587
755
|
"""List doc ids in the given tier (and optional category), ordered by id.
|
|
588
756
|
|
|
589
757
|
verbose: False (default) drops per-entry `path` (fetch via kb_get(id)) and
|
|
@@ -592,11 +760,11 @@ def build_app(
|
|
|
592
760
|
resp = kb_list_fn(idx=state.idx, tier=tier, category=category)
|
|
593
761
|
return shape_response(resp, verbose=verbose)
|
|
594
762
|
|
|
595
|
-
@app.tool()
|
|
763
|
+
@app.tool(title="KB Onboarding Status", annotations=READ_ONLY_TOOL)
|
|
596
764
|
def kb_onboarding_status(
|
|
597
|
-
workspace:
|
|
598
|
-
workspace_remote_url:
|
|
599
|
-
component_remote_url:
|
|
765
|
+
workspace: WorkspaceParam, component: ComponentParam = None,
|
|
766
|
+
workspace_remote_url: RemoteUrlParam = None,
|
|
767
|
+
component_remote_url: RemoteUrlParam = None,
|
|
600
768
|
) -> dict[str, object]:
|
|
601
769
|
"""Compute onboarding status for a workspace + optional component.
|
|
602
770
|
State is one of: absent, partial, onboarded, rename_candidate."""
|
|
@@ -632,10 +800,10 @@ def build_app(
|
|
|
632
800
|
"error": "too many requests; retry later"}
|
|
633
801
|
return None
|
|
634
802
|
|
|
635
|
-
@app.tool()
|
|
803
|
+
@app.tool(title="KB Cleanup Plan", annotations=READ_ONLY_TOOL)
|
|
636
804
|
def kb_cleanup_plan(
|
|
637
|
-
workspace:
|
|
638
|
-
component:
|
|
805
|
+
workspace: WorkspaceParam, local_files: LocalFilesParam,
|
|
806
|
+
component: ComponentParam = None, jaccard_threshold: JaccardParam = 0.6,
|
|
639
807
|
) -> dict[str, object]:
|
|
640
808
|
"""Read-only. Classify local project-repo docs against KB content for this
|
|
641
809
|
workspace/component and return thin-pointer replacements for duplicates.
|
|
@@ -657,11 +825,11 @@ def build_app(
|
|
|
657
825
|
if not read_only:
|
|
658
826
|
# Write + enforcement-write surface. A read-only replica
|
|
659
827
|
# (issue #44) exposes none of these tools.
|
|
660
|
-
@app.tool()
|
|
828
|
+
@app.tool(title="KB Propose Memory", annotations=PROPOSAL_WRITE_TOOL)
|
|
661
829
|
def kb_propose_memory(
|
|
662
|
-
text:
|
|
663
|
-
agent_identity:
|
|
664
|
-
evidence:
|
|
830
|
+
text: TextParam, tags: TagsParam, source_session: SourceSessionParam,
|
|
831
|
+
agent_identity: AgentIdentityParam, confidence: ConfidenceParam,
|
|
832
|
+
evidence: EvidenceParam = None,
|
|
665
833
|
) -> dict[str, object]:
|
|
666
834
|
"""Propose a new memory file. High confidence auto-commits and
|
|
667
835
|
enqueues for push; low confidence enters the pending queue for operator
|
|
@@ -693,12 +861,13 @@ def build_app(
|
|
|
693
861
|
)
|
|
694
862
|
return resp.model_dump()
|
|
695
863
|
|
|
696
|
-
@app.tool()
|
|
864
|
+
@app.tool(title="KB Propose Edit", annotations=PROPOSAL_EDIT_TOOL)
|
|
697
865
|
def kb_propose_edit(
|
|
698
|
-
target_path:
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
866
|
+
target_path: TargetPathParam, postimage: PostimageParam,
|
|
867
|
+
base_commit: BaseCommitParam, base_blob_sha: BaseBlobShaParam,
|
|
868
|
+
target_file_hash: TargetFileHashParam, reason: ReasonParam,
|
|
869
|
+
source_session: SourceSessionParam, agent_identity: AgentIdentityParam,
|
|
870
|
+
confidence: ConfidenceParam, evidence: EvidenceParam = None,
|
|
702
871
|
) -> dict[str, object]:
|
|
703
872
|
"""Propose an edit to an existing (or new) markdown file under an
|
|
704
873
|
indexed tier. High confidence auto-commits + queues for push; low
|
|
@@ -733,11 +902,13 @@ def build_app(
|
|
|
733
902
|
)
|
|
734
903
|
return resp.model_dump()
|
|
735
904
|
|
|
736
|
-
@app.tool()
|
|
905
|
+
@app.tool(title="KB Resolve Pending", annotations=DESTRUCTIVE_WRITE_TOOL)
|
|
737
906
|
def kb_resolve_pending(
|
|
738
|
-
pending_id:
|
|
739
|
-
|
|
740
|
-
|
|
907
|
+
pending_id: PendingIdParam, decision: DecisionParam,
|
|
908
|
+
edited_text: EditedTextParam = None,
|
|
909
|
+
source_session: SourceSessionParam = "operator-resolve",
|
|
910
|
+
agent_identity: AgentIdentityParam = "operator",
|
|
911
|
+
override_secret_scan: OverrideSecretScanParam = False,
|
|
741
912
|
) -> dict[str, object]:
|
|
742
913
|
"""Resolve a pending proposal: approve (optionally with edited text) or
|
|
743
914
|
reject. Approval commits + enqueues for push.
|
|
@@ -769,7 +940,7 @@ def build_app(
|
|
|
769
940
|
)
|
|
770
941
|
return resp.model_dump()
|
|
771
942
|
|
|
772
|
-
@app.tool()
|
|
943
|
+
@app.tool(title="KB List Pending", annotations=READ_ONLY_TOOL)
|
|
773
944
|
def kb_list_pending() -> dict[str, object]:
|
|
774
945
|
"""List currently pending proposals awaiting operator decision."""
|
|
775
946
|
assert state.pending is not None
|
|
@@ -777,10 +948,10 @@ def build_app(
|
|
|
777
948
|
resp = kb_list_pending_fn(pending=state.pending)
|
|
778
949
|
return resp.model_dump()
|
|
779
950
|
|
|
780
|
-
@app.tool()
|
|
951
|
+
@app.tool(title="KB Audit", annotations=READ_ONLY_TOOL)
|
|
781
952
|
def kb_audit(
|
|
782
|
-
since:
|
|
783
|
-
status:
|
|
953
|
+
since: SinceParam = None, agent: AgentParam = None,
|
|
954
|
+
status: EventStatusParam = None, limit: AuditLimitParam = 100,
|
|
784
955
|
) -> dict[str, object]:
|
|
785
956
|
"""Return recent audit events, most-recent first. Optional filters:
|
|
786
957
|
since (unix ts), agent (agent_identity), status (event status)."""
|
|
@@ -790,8 +961,8 @@ def build_app(
|
|
|
790
961
|
agent=agent, status=status, limit=limit)
|
|
791
962
|
return resp.model_dump()
|
|
792
963
|
|
|
793
|
-
@app.tool()
|
|
794
|
-
def kb_session_recap(source_session:
|
|
964
|
+
@app.tool(title="KB Session Recap", annotations=READ_ONLY_TOOL)
|
|
965
|
+
def kb_session_recap(source_session: SourceSessionParam) -> dict[str, object]:
|
|
795
966
|
"""Read-only per-session write summary (issue #112 feedback loop):
|
|
796
967
|
N committed, M demoted-to-pending, K rejected for source_session.
|
|
797
968
|
Call this (or `kb pending`) whenever a write response indicated a
|
|
@@ -804,13 +975,13 @@ def build_app(
|
|
|
804
975
|
resp = kb_session_recap_fn(audit_log=state.audit_log, source_session=source_session)
|
|
805
976
|
return resp.model_dump()
|
|
806
977
|
|
|
807
|
-
@app.tool()
|
|
978
|
+
@app.tool(title="KB Bootstrap Project", annotations=PROPOSAL_WRITE_TOOL)
|
|
808
979
|
def kb_bootstrap_project(
|
|
809
|
-
workspace:
|
|
810
|
-
source_session:
|
|
811
|
-
|
|
812
|
-
workspace_remote_url:
|
|
813
|
-
component_remote_url:
|
|
980
|
+
workspace: WorkspaceParam, files: BootstrapFilesParam,
|
|
981
|
+
source_session: SourceSessionParam, agent_identity: AgentIdentityParam,
|
|
982
|
+
confidence: ConfidenceParam, component: ComponentParam = None,
|
|
983
|
+
workspace_remote_url: RemoteUrlParam = None,
|
|
984
|
+
component_remote_url: RemoteUrlParam = None,
|
|
814
985
|
) -> dict[str, object]:
|
|
815
986
|
"""Bootstrap a new workspace/component. Only valid when status=absent
|
|
816
987
|
or partial. High confidence commits atomically; low confidence
|
|
@@ -842,10 +1013,11 @@ def build_app(
|
|
|
842
1013
|
)
|
|
843
1014
|
return resp.model_dump()
|
|
844
1015
|
|
|
845
|
-
@app.tool()
|
|
1016
|
+
@app.tool(title="KB Consult", annotations=STATEFUL_NONDESTRUCTIVE_TOOL)
|
|
846
1017
|
def kb_consult(
|
|
847
|
-
workspace:
|
|
848
|
-
|
|
1018
|
+
workspace: WorkspaceParam, intent: QueryParam,
|
|
1019
|
+
source_session: SourceSessionParam,
|
|
1020
|
+
agent_identity: AgentIdentityParam, trigger: TriggerParam = "explicit",
|
|
849
1021
|
) -> dict[str, object]:
|
|
850
1022
|
"""Record a consultation for (source_session, workspace) and return the
|
|
851
1023
|
governing rules for the intent. Call before code/architectural work.
|
|
@@ -876,10 +1048,11 @@ def build_app(
|
|
|
876
1048
|
)
|
|
877
1049
|
return resp.model_dump(exclude_none=True)
|
|
878
1050
|
|
|
879
|
-
@app.tool()
|
|
1051
|
+
@app.tool(title="KB Gate Check", annotations=STATEFUL_NONDESTRUCTIVE_TOOL)
|
|
880
1052
|
def kb_gate_check(
|
|
881
|
-
workspace:
|
|
882
|
-
|
|
1053
|
+
workspace: WorkspaceParam, session_id: SessionIdParam,
|
|
1054
|
+
tool_name: ToolNameParam, action_path: ActionPathParam = None,
|
|
1055
|
+
action_diff: ActionDiffParam = "",
|
|
883
1056
|
) -> dict[str, object]:
|
|
884
1057
|
"""Return a verdict (allow | consult_required) for a pending code action.
|
|
885
1058
|
Governed actions require a fresh consultation on record."""
|
|
@@ -897,9 +1070,9 @@ def build_app(
|
|
|
897
1070
|
)
|
|
898
1071
|
return resp.model_dump()
|
|
899
1072
|
|
|
900
|
-
@app.tool()
|
|
1073
|
+
@app.tool(title="KB Compliance", annotations=READ_ONLY_TOOL)
|
|
901
1074
|
def kb_compliance(
|
|
902
|
-
since:
|
|
1075
|
+
since: SinceParam = None, agent: AgentParam = None,
|
|
903
1076
|
) -> dict[str, object]:
|
|
904
1077
|
"""Aggregate enforcement events (consult / gate_*) overall and per agent."""
|
|
905
1078
|
if state.audit_log is None:
|
|
@@ -908,10 +1081,11 @@ def build_app(
|
|
|
908
1081
|
resp = kb_compliance_fn(audit_log=state.audit_log, since=since, agent=agent)
|
|
909
1082
|
return resp.model_dump()
|
|
910
1083
|
|
|
911
|
-
@app.tool()
|
|
1084
|
+
@app.tool(title="KB Record Event", annotations=STATEFUL_NONDESTRUCTIVE_TOOL)
|
|
912
1085
|
def kb_record_event(
|
|
913
|
-
event_type:
|
|
914
|
-
|
|
1086
|
+
event_type: EventTypeParam, workspace: WorkspaceParam,
|
|
1087
|
+
agent_identity: AgentIdentityParam, source_session: SourceSessionParam,
|
|
1088
|
+
reason: ReasonParam = "",
|
|
915
1089
|
) -> dict[str, object]:
|
|
916
1090
|
"""Record a gate_bypass or gate_degraded enforcement event in the audit."""
|
|
917
1091
|
if state.audit_log is None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{data_olympus-0.4.1 → data_olympus-0.4.2}/example-bundle/projects/acme-app/components/api/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|