data-olympus 0.3.3__tar.gz → 0.3.5__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.3.3 → data_olympus-0.3.5}/CHANGELOG.md +59 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/PKG-INFO +1 -1
- {data_olympus-0.3.3 → data_olympus-0.3.5}/pyproject.toml +1 -1
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/auth.py +26 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/enforce_policy.py +15 -1
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/tools_write.py +12 -4
- {data_olympus-0.3.3 → data_olympus-0.3.5}/.gitignore +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/LICENSE +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/NOTICE +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/SPEC.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/benchmarks/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/bin/_kb_detect_workspace.sh +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/bin/_kb_enforce.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/bin/_kb_fallback.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/bin/kb +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/bin/kb-enforce-hook +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/bin/opencode/data-olympus-gate.ts +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/deploy/k8s/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/deploy/k8s/read-replica/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/docs/releases/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/example-bundle/projects/acme-app/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/example-bundle/projects/acme-app/components/api/README.md +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/__init__.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/audit_log.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/audit_trailers.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/cli/__init__.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/cli/import_cmd.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/cli/indexgen.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/cli/main.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/cli/report_cmd.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/config.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/cooccurrence.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/dedup.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/durable.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/embeddings.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/format/__init__.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/format/document.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/format/frontmatter.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/format/lint.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/format/validate.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/git_ops.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/health.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/__init__.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/adr.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/flat.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/model.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/okf.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/run.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/importer/stamp.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/index.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/markdown_parse.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/models.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/onboarding.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/onboarding_inflight.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/onboarding_playbook.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/pending.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/principals.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/prompts.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/push_queue.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/query_expansion.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/rate_limit.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/refresh.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/report.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/rest_api.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/safe_id.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/search_gate.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/search_shortcut.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/server.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/session_metrics.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/setup_wizard.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/thin_pointer.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/tools_audit.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/tools_enforce.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/tools_onboarding.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/tools_read.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/trigram.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/viewer/__init__.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/viewer/generator.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/worktrees.py +0 -0
- {data_olympus-0.3.3 → data_olympus-0.3.5}/src/data_olympus/write_gate.py +0 -0
|
@@ -12,6 +12,65 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.3.5] - 2026-07-06
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Ambiguous rejection reason for writes outside the deployment's indexed
|
|
20
|
+
prefixes.** `kb_propose_edit` (and `kb_propose_memory`) rejected a
|
|
21
|
+
well-formed, non-malicious path outside `KB_INDEXED_PREFIXES` with the same
|
|
22
|
+
opaque reason string (`traversal_or_excluded` / `not_md_or_excluded`) used
|
|
23
|
+
for an actual traversal or control-character attempt. A syntactically fine
|
|
24
|
+
path outside the configured prefixes is an ordinary deployment-configuration
|
|
25
|
+
fact, not a structural or security rejection - conflating the two sent an
|
|
26
|
+
operator hunting for a nonexistent traversal bug during a company-knowledge
|
|
27
|
+
KB audit session (`operator/laptop.md`, readable and indexed for search,
|
|
28
|
+
came back rejected on write with no indication it was simply outside the
|
|
29
|
+
deployment's writable-prefix allowlist). `rejected_path_not_indexable`
|
|
30
|
+
responses (and audit events) now carry a specific reason:
|
|
31
|
+
`structurally_invalid` (empty/control-chars/absolute/traversal/excluded
|
|
32
|
+
segment), `not_markdown`, or `not_in_indexed_prefixes`.
|
|
33
|
+
- Investigated the same session's second report (a `rejected_stale_base`
|
|
34
|
+
resolve appearing to "leak" its path lock) and confirmed it is **not a
|
|
35
|
+
bug**: `restore_resolve` deliberately keeps the path lock held after a
|
|
36
|
+
gate rejection so the operator's still-pending proposal can't be clobbered
|
|
37
|
+
by a concurrent write to the same path (documented behavior, hardened
|
|
38
|
+
across multiple rounds of review). `reject`ing the stale entry (which does
|
|
39
|
+
release the lock) and re-proposing fresh, exactly what that session did, is
|
|
40
|
+
the correct and only intended recovery for a pending entry with a
|
|
41
|
+
permanently-wrong base marker - there was nothing to fix.
|
|
42
|
+
|
|
43
|
+
## [0.3.4] - 2026-07-05
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **Enforcement gate false-positives on prose that merely mentions a governed
|
|
48
|
+
keyword, for tool calls with a known file path.** `IntentClassifier.classify`
|
|
49
|
+
scanned `action_diff` (the raw Write/Edit content or Bash command text, up
|
|
50
|
+
to 4000 chars) for free-text keywords like `auth`, `rls`, `tenant`, `secret`
|
|
51
|
+
in addition to `intent`, for every tool call. A scratch write whose text
|
|
52
|
+
simply discussed one of those words as a topic - not an actual governed
|
|
53
|
+
action - tripped the pre-tool gate regardless of the real (ungoverned)
|
|
54
|
+
write target, and a fresh `kb_consult` call did not clear it because the
|
|
55
|
+
consult's own classification (`intent`-only) and the gate's classification
|
|
56
|
+
(`intent + action_diff`) disagreed on whether the action was governed.
|
|
57
|
+
Keyword matching on `action_diff` now only applies when the tool call
|
|
58
|
+
carries no `action_path` (Bash commands and OpenCode's `patch`, whose
|
|
59
|
+
content is the only signal available); when an `action_path` is present
|
|
60
|
+
(Write/Edit/MultiEdit), classification uses the path globs and dependency
|
|
61
|
+
command patterns, unchanged, so this keeps full detection for real governed
|
|
62
|
+
edits (a patch touching `package.json`) without flagging unrelated prose in
|
|
63
|
+
files the path globs don't consider governed. Found via a company-knowledge
|
|
64
|
+
KB audit session that reproduced it live (a scratch `.txt` file containing
|
|
65
|
+
"RLS tenant boundaries auth trust model" was blocked with no governed path
|
|
66
|
+
or command in sight); independently reviewed via `codex exec` before
|
|
67
|
+
release.
|
|
68
|
+
- The audit's other finding (CX1: the pre-tool gate and the pre-commit
|
|
69
|
+
`report --staged` gate resolving two different workspace keys) was
|
|
70
|
+
already fixed in v0.2.0 (#64, worktree-invariant workspace key); no
|
|
71
|
+
further change needed. It was still visible in that audit only because
|
|
72
|
+
it predated the deployed kn-dev redeploy.
|
|
73
|
+
|
|
15
74
|
## [0.3.3] - 2026-07-05
|
|
16
75
|
|
|
17
76
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-olympus
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
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
|
|
@@ -95,6 +95,32 @@ def is_writable_path(target_path: str) -> bool:
|
|
|
95
95
|
return any(canonical.startswith(p) for p in indexed_prefixes())
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
def path_rejection_reason(raw_target_path: str) -> str:
|
|
99
|
+
"""Why a path fails :func:`is_writable_path`, for a caller that already knows
|
|
100
|
+
it does. Three distinct causes were previously collapsed into one opaque
|
|
101
|
+
reason string (``not_md_or_excluded`` / ``traversal_or_excluded``), which
|
|
102
|
+
reads identically whether the path was a malicious traversal attempt or a
|
|
103
|
+
perfectly legitimate path outside the deployment's configured
|
|
104
|
+
``KB_INDEXED_PREFIXES`` - the latter is an ordinary, expected outcome for any
|
|
105
|
+
deployment whose repo layout has top-level directories beyond the generic
|
|
106
|
+
default (see ``indexed_prefixes()``), not a security event, and conflating
|
|
107
|
+
the two sent at least one operator hunting for a traversal bug that was
|
|
108
|
+
actually a missing ``KB_INDEXED_PREFIXES`` entry.
|
|
109
|
+
|
|
110
|
+
Returns one of: ``structurally_invalid`` (empty/control-chars/absolute/
|
|
111
|
+
traversal/excluded-segment - :func:`normalize_target_path` rejected it),
|
|
112
|
+
``not_markdown``, or ``not_in_indexed_prefixes`` (a syntactically fine path
|
|
113
|
+
outside every configured prefix - a deployment-configuration gap, not a
|
|
114
|
+
structural or security rejection).
|
|
115
|
+
"""
|
|
116
|
+
canonical = normalize_target_path(raw_target_path)
|
|
117
|
+
if canonical is None:
|
|
118
|
+
return "structurally_invalid"
|
|
119
|
+
if not canonical.endswith(".md"):
|
|
120
|
+
return "not_markdown"
|
|
121
|
+
return "not_in_indexed_prefixes"
|
|
122
|
+
|
|
123
|
+
|
|
98
124
|
def safe_join_under_root(root: str, target_path: str) -> str | None:
|
|
99
125
|
"""Join ``target_path`` under ``root`` and return the absolute path only when
|
|
100
126
|
it resolves to exactly that lexical location inside ``root``; else return None.
|
|
@@ -81,7 +81,21 @@ class IntentClassifier:
|
|
|
81
81
|
action_diff: str = "",
|
|
82
82
|
) -> ClassifyResult:
|
|
83
83
|
signals: list[str] = []
|
|
84
|
-
text
|
|
84
|
+
# Free-text keyword signals always scan `intent` (a short, deliberate
|
|
85
|
+
# statement of what the agent is about to do - kb_consult's argument -
|
|
86
|
+
# so a keyword there is a real signal). They scan `action_diff` too,
|
|
87
|
+
# but ONLY when there is no `action_path`: `action_diff` is arbitrary
|
|
88
|
+
# write/command CONTENT up to 4000 chars (Write.content, Edit.new_string,
|
|
89
|
+
# Bash.command, OpenCode patch text), and when a concrete action_path is
|
|
90
|
+
# already given, path-glob matching below is the intended signal for
|
|
91
|
+
# that action - layering the free-text net on top blocked benign writes
|
|
92
|
+
# to ungoverned files that merely discussed a keyword as a topic (a
|
|
93
|
+
# scratch file quoting "RLS"/"auth" from a security standard). Bash
|
|
94
|
+
# commands and OpenCode's `patch` tool carry no action_path at all
|
|
95
|
+
# (their action_diff IS the only content available), so the free-text
|
|
96
|
+
# net still applies there - dropping it entirely would silently lose
|
|
97
|
+
# the only governed-decision signal for those two tool shapes.
|
|
98
|
+
text = intent.lower() if action_path else f"{intent} {action_diff}".lower()
|
|
85
99
|
for kw, rx in self._keyword_res:
|
|
86
100
|
if rx.search(text):
|
|
87
101
|
signals.append(f"keyword:{kw}")
|
|
@@ -15,6 +15,7 @@ from data_olympus.auth import (
|
|
|
15
15
|
PathBlocklist,
|
|
16
16
|
is_writable_path,
|
|
17
17
|
normalize_target_path,
|
|
18
|
+
path_rejection_reason,
|
|
18
19
|
safe_join_under_root,
|
|
19
20
|
)
|
|
20
21
|
from data_olympus.format.frontmatter import parse_frontmatter
|
|
@@ -486,11 +487,12 @@ def kb_propose_memory_fn(
|
|
|
486
487
|
|
|
487
488
|
# 1. Structural rule (cheap).
|
|
488
489
|
if not is_writable_path(target_path):
|
|
490
|
+
reason = path_rejection_reason(target_path)
|
|
489
491
|
_emit_audit(audit_log, **{**audit_base, "status": "rejected_path_not_indexable",
|
|
490
|
-
"reason":
|
|
492
|
+
"reason": reason})
|
|
491
493
|
return ProposeResponse(
|
|
492
494
|
status="rejected_path_not_indexable",
|
|
493
|
-
reason=
|
|
495
|
+
reason=reason,
|
|
494
496
|
target_path=target_path,
|
|
495
497
|
)
|
|
496
498
|
|
|
@@ -657,9 +659,15 @@ def kb_propose_edit_fn(
|
|
|
657
659
|
}
|
|
658
660
|
canonical = normalize_target_path(target_path)
|
|
659
661
|
if canonical is None or not is_writable_path(canonical):
|
|
660
|
-
|
|
662
|
+
# audit_base["reason"] otherwise carries the caller's edit rationale;
|
|
663
|
+
# for this terminal rejection (nothing is written) it is overwritten
|
|
664
|
+
# with the machine rejection cause instead, since that is what an
|
|
665
|
+
# operator reading the audit log needs to see here.
|
|
666
|
+
rejection_reason = path_rejection_reason(target_path)
|
|
667
|
+
_emit_audit(audit_log, **{**audit_base, "status": "rejected_path_not_indexable",
|
|
668
|
+
"reason": rejection_reason})
|
|
661
669
|
return ProposeResponse(status="rejected_path_not_indexable",
|
|
662
|
-
reason=
|
|
670
|
+
reason=rejection_reason,
|
|
663
671
|
target_path=target_path)
|
|
664
672
|
# From here on operate ONLY on the canonical path: classification, blocklist,
|
|
665
673
|
# the pending record, the filesystem join, and ``git add`` must all agree with
|
|
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.3.3 → data_olympus-0.3.5}/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
|