observed-knowledge-ledger 0.6.0__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.
- observed_knowledge_ledger-0.6.0/.claude/agent-memory/architecture-reviewer/MEMORY.md +1 -0
- observed_knowledge_ledger-0.6.0/.claude/agent-memory/architecture-reviewer/review_findings_log.md +15 -0
- observed_knowledge_ledger-0.6.0/.claude/agents/architecture-reviewer.md +41 -0
- observed_knowledge_ledger-0.6.0/.claude/hooks/stop-okl-encode.sh +78 -0
- observed_knowledge_ledger-0.6.0/.claude/hooks/userpromptsubmit-okl-check.sh +70 -0
- observed_knowledge_ledger-0.6.0/.claude/settings.json +24 -0
- observed_knowledge_ledger-0.6.0/.coverage +0 -0
- observed_knowledge_ledger-0.6.0/.github/ISSUE_TEMPLATE/bug_report.md +21 -0
- observed_knowledge_ledger-0.6.0/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- observed_knowledge_ledger-0.6.0/.github/dependabot.yml +32 -0
- observed_knowledge_ledger-0.6.0/.github/pull_request_template.md +15 -0
- observed_knowledge_ledger-0.6.0/.github/workflows/ci.yml +100 -0
- observed_knowledge_ledger-0.6.0/.github/workflows/okl-verify.yml +120 -0
- observed_knowledge_ledger-0.6.0/.github/workflows/publish.yml +99 -0
- observed_knowledge_ledger-0.6.0/.gitignore +23 -0
- observed_knowledge_ledger-0.6.0/AGENTS.md +74 -0
- observed_knowledge_ledger-0.6.0/CHANGELOG.md +248 -0
- observed_knowledge_ledger-0.6.0/CLAUDE.md +74 -0
- observed_knowledge_ledger-0.6.0/CONTRIBUTING.md +111 -0
- observed_knowledge_ledger-0.6.0/LICENSE +21 -0
- observed_knowledge_ledger-0.6.0/PKG-INFO +791 -0
- observed_knowledge_ledger-0.6.0/README.md +757 -0
- observed_knowledge_ledger-0.6.0/SECURITY.md +56 -0
- observed_knowledge_ledger-0.6.0/ci/check-diagram-figures.sh +82 -0
- observed_knowledge_ledger-0.6.0/ci/okl-verify.yml +120 -0
- observed_knowledge_ledger-0.6.0/ci/review-agent.sh +133 -0
- observed_knowledge_ledger-0.6.0/docs/DEPLOY.md +171 -0
- observed_knowledge_ledger-0.6.0/docs/ab-results-chart.png +0 -0
- observed_knowledge_ledger-0.6.0/docs/ab-results-chart.svg +27 -0
- observed_knowledge_ledger-0.6.0/docs/decisions/2026-07-17-flat-retrieval-until-scale.md +55 -0
- observed_knowledge_ledger-0.6.0/docs/decisions/2026-07-21-subject-tags-controlled-vocabulary.md +100 -0
- observed_knowledge_ledger-0.6.0/docs/okl-how-it-works.excalidraw +3053 -0
- observed_knowledge_ledger-0.6.0/docs/okl-how-it-works.svg +2 -0
- observed_knowledge_ledger-0.6.0/docs/okl-retrieval-pipeline.svg +89 -0
- observed_knowledge_ledger-0.6.0/docs/okl-sixth-surface.excalidraw +3819 -0
- observed_knowledge_ledger-0.6.0/docs/okl-sixth-surface.svg +2 -0
- observed_knowledge_ledger-0.6.0/docs/posts/01-memory-that-outlives-the-run.md +65 -0
- observed_knowledge_ledger-0.6.0/docs/posts/02-dont-let-a-step-grade-itself.md +45 -0
- observed_knowledge_ledger-0.6.0/docs/posts/03-enforcement-or-good-intentions.md +37 -0
- observed_knowledge_ledger-0.6.0/docs/posts/04-the-elegant-change-was-the-wrong-one.md +55 -0
- observed_knowledge_ledger-0.6.0/docs/render_pipeline_diagram.py +287 -0
- observed_knowledge_ledger-0.6.0/evals/README.md +49 -0
- observed_knowledge_ledger-0.6.0/evals/REPORT.md +838 -0
- observed_knowledge_ledger-0.6.0/evals/ab_harness.py +324 -0
- observed_knowledge_ledger-0.6.0/evals/preflight.py +94 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260829-2300.json +41 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260829-2315.json +137 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260830-0003.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260830-0148.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260901-0133.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260901-1238.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260902-0538.json +439 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260902-1216.json +27 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-0157.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-0308.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-1214.json +441 -0
- observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-1323.json +443 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/README.md +23 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/briefed-userpromptsubmit-lint.yml +27 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/control-lint.yml +22 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/hook.log +3 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/service-record-500.log +47 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/session-briefed-pretooluse.txt +3 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/session-briefed-userpromptsubmit.txt +7 -0
- observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/session-control.txt +3 -0
- observed_knowledge_ledger-0.6.0/evals/tasks.jsonl +8 -0
- observed_knowledge_ledger-0.6.0/gates/check-canon-size.sh +11 -0
- observed_knowledge_ledger-0.6.0/gates/check-diagram-pairs.sh +39 -0
- observed_knowledge_ledger-0.6.0/gates/check-doc-orphans.sh +23 -0
- observed_knowledge_ledger-0.6.0/gates/check-links.sh +41 -0
- observed_knowledge_ledger-0.6.0/gates/check-retractions.sh +22 -0
- observed_knowledge_ledger-0.6.0/gates/check-tombstones.sh +22 -0
- observed_knowledge_ledger-0.6.0/gates/run-gates.sh +33 -0
- observed_knowledge_ledger-0.6.0/hooks/stop-okl-encode.sh +93 -0
- observed_knowledge_ledger-0.6.0/hooks/userpromptsubmit-okl-check.sh +70 -0
- observed_knowledge_ledger-0.6.0/okl-drift.json +105 -0
- observed_knowledge_ledger-0.6.0/packaging/org-knowledge-layer/README.md +14 -0
- observed_knowledge_ledger-0.6.0/packaging/org-knowledge-layer/pyproject.toml +37 -0
- observed_knowledge_ledger-0.6.0/pyproject.toml +118 -0
- observed_knowledge_ledger-0.6.0/seed/dotnet-canon.json +497 -0
- observed_knowledge_ledger-0.6.0/seed/dotnet-decisions.json +339 -0
- observed_knowledge_ledger-0.6.0/seed/dotnet-defects.json +135 -0
- observed_knowledge_ledger-0.6.0/seed/dotnet-review-surfaces.json +147 -0
- observed_knowledge_ledger-0.6.0/seed/frontend-canon.json +118 -0
- observed_knowledge_ledger-0.6.0/seed/geospatial-deeptime-defects.json +60 -0
- observed_knowledge_ledger-0.6.0/seed/geospatial-defects.json +158 -0
- observed_knowledge_ledger-0.6.0/seed/geospatial-enforcement-defects.json +122 -0
- observed_knowledge_ledger-0.6.0/seed/geospatial-eval-defects.json +25 -0
- observed_knowledge_ledger-0.6.0/seed/rag-defects.json +120 -0
- observed_knowledge_ledger-0.6.0/seed/react-defects.json +47 -0
- observed_knowledge_ledger-0.6.0/src/okl/__init__.py +12 -0
- observed_knowledge_ledger-0.6.0/src/okl/__main__.py +8 -0
- observed_knowledge_ledger-0.6.0/src/okl/bootstrap.py +89 -0
- observed_knowledge_ledger-0.6.0/src/okl/cli.py +1009 -0
- observed_knowledge_ledger-0.6.0/src/okl/client.py +279 -0
- observed_knowledge_ledger-0.6.0/src/okl/coexist.py +148 -0
- observed_knowledge_ledger-0.6.0/src/okl/core.py +609 -0
- observed_knowledge_ledger-0.6.0/src/okl/drift.py +278 -0
- observed_knowledge_ledger-0.6.0/src/okl/mcp_server.py +112 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/MANIFEST.md +67 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/dependabot.yml +17 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/method-gates.yml +74 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/okl-verify.yml +120 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/review-agent.sh +133 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/agents/architecture-reviewer.md +41 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/check-rules.md +24 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/feature-spec.md +37 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/seed-from-codebase.md +90 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/seed-from-docs.md +123 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/rules/example-area.md +22 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +40 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/skills/encoding-loop/SKILL.md +54 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +56 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/evals/README.md +32 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/evals/cases.jsonl +1 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/evals/run_evals.py +109 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-canon-size.sh +11 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-diagram-pairs.sh +39 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-doc-orphans.sh +23 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-links.sh +41 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-retractions.sh +22 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-tombstones.sh +22 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/run-gates.sh +33 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/hooks/hooks.json +16 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/hooks/stop-okl-encode.sh +93 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/hooks/userpromptsubmit-okl-check.sh +70 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/plugin/plugin.json +10 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/README.md +12 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/architecture.md +55 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/messaging.md +31 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/performance-and-data.md +36 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/security.md +42 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/geospatial/README.md +6 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +38 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/README.md +13 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +37 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/rules/project-structure.md +28 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +73 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/react/README.md +18 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/react/rules/frontend.md +57 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/registries/RETRACTIONS.md +19 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/registries/tombstones.txt +7 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/root/CLAUDE.md +55 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold/root/METHOD.md +64 -0
- observed_knowledge_ledger-0.6.0/src/okl/scaffold_cmd.py +129 -0
- observed_knowledge_ledger-0.6.0/src/okl/seed.py +119 -0
- observed_knowledge_ledger-0.6.0/src/okl/service.py +186 -0
- observed_knowledge_ledger-0.6.0/src/okl/store.py +619 -0
- observed_knowledge_ledger-0.6.0/tests/test_okl.py +2133 -0
- observed_knowledge_ledger-0.6.0/tests/test_scaffold.py +877 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- [Review findings log](review_findings_log.md) — recurring finding classes with counts; promote to a gate at 3
|
observed_knowledge_ledger-0.6.0/.claude/agent-memory/architecture-reviewer/review_findings_log.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-findings-log
|
|
3
|
+
description: Recurring architecture-review finding classes in okl, with counts, to decide when to promote one to a mechanical gate
|
|
4
|
+
metadata:
|
|
5
|
+
type: project
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Tally of finding classes seen in okl reviews. At 3 occurrences, propose a gate via encoding-loop.
|
|
9
|
+
|
|
10
|
+
- **_Backend Protocol method added without a behavioural clause in its docstring** — 1 (PR #35, `edges()`, 2026-09-24). Related record: "A port is defined by behaviour, not by its signatures".
|
|
11
|
+
- **Old and new computation of one metric both served (two sources of truth)** — 1 (PR #35: `recurrence_after_arming` SQL kept beside `core.recurrence_report`).
|
|
12
|
+
- **Postgres-backend change reaches CI untested** — 1 (PR #35). CI has no Postgres service; `test_postgres_backend_conformance` is skipped unless OKL_TEST_POSTGRES_URL is set.
|
|
13
|
+
|
|
14
|
+
**Why:** the agent brief asks to promote a finding class to a gate at its third sighting.
|
|
15
|
+
**How to apply:** bump the count when a class recurs; at 3, recommend a gate (e.g. a test that every _Backend method is named in the Protocol docstring).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architecture-reviewer
|
|
3
|
+
description: Reviews a diff or a proposed change against this repo's encoded rules and the org knowledge layer. Invoke on non-pattern-conforming changes (new bounded context, novel dependency, security-model change, multi-step refactor) and before merging anything touching published results. Returns a pattern-checklist verdict, not a rewrite.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: inherit
|
|
6
|
+
memory: project # persists to .claude/agent-memory/architecture-reviewer/ (committed, team-shared)
|
|
7
|
+
skills:
|
|
8
|
+
- encoding-loop
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Architecture reviewer
|
|
12
|
+
|
|
13
|
+
You are a reviewer, not an implementer. You read the change and judge it against the encoded body —
|
|
14
|
+
you do not rewrite it. Your output is a checklist verdict with specific file:line findings.
|
|
15
|
+
|
|
16
|
+
## Before you start
|
|
17
|
+
1. Run `okl check --task "<one-line summary of the change under review>"` and treat the returned
|
|
18
|
+
armed gates, retractions, tombstones, and THREAT prior-art as binding review criteria.
|
|
19
|
+
2. Read `.claude/rules/` entries whose `paths:` match the changed files.
|
|
20
|
+
|
|
21
|
+
## Pattern checklist (extend as the repo encodes new rules)
|
|
22
|
+
- **Smallest-surface / speculative coupling** — is there an abstraction (interface, factory, layer)
|
|
23
|
+
with exactly one implementation and no test substitution and no concrete second impl on the
|
|
24
|
+
roadmap? If so, flag it as speculative — the concrete class should be used directly.
|
|
25
|
+
- **Assert-from-memory** — does any spec/config/scaffold claim something is "correct"/"valid"
|
|
26
|
+
without a mechanical `validate` step? Flag it; this is the #14-class defect.
|
|
27
|
+
- **Retracted claim restated** — does any prose restate a claim in `registries/RETRACTIONS.md`
|
|
28
|
+
without also retracting it? Fail.
|
|
29
|
+
- **Resurrected identifier** — does the diff reintroduce anything in `registries/tombstones.txt`? Fail.
|
|
30
|
+
- **Missing gate receipt** — does a fix reference a defect class that has a gate, without the gate
|
|
31
|
+
running in CI on this change?
|
|
32
|
+
- **Report-the-result** — if this change is a "fix" that confirms a hypothesis, was the control run?
|
|
33
|
+
|
|
34
|
+
<!-- <<FILL: STACK-SPECIFIC REVIEW CHECKS>>
|
|
35
|
+
Add checks specific to this stack (e.g. IDOR predicate in the SQL Where clause; N+1 queries;
|
|
36
|
+
async-over-sync; mass-assignment of server-controlled fields). Keep each as one scan rule. -->
|
|
37
|
+
|
|
38
|
+
## Memory
|
|
39
|
+
Use your persistent memory dir to accumulate this repo's recurring findings and architectural
|
|
40
|
+
decisions across reviews. When you see the same finding class a third time, propose promoting it to
|
|
41
|
+
a mechanical gate (surface 5) via the encoding-loop skill.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Stop hook — the write-side mechanical catch for the encoding loop.
|
|
3
|
+
#
|
|
4
|
+
# The read side (okl check) is enforced by the PreToolUse hook; nothing enforced the WRITE
|
|
5
|
+
# side, so a session could end without recording what it learned ("a merged fix without the
|
|
6
|
+
# rule is a half-finished job"). This hook asks the question at the ship moment, once:
|
|
7
|
+
# if the session changed the working tree, block the first stop (exit 2) with a prompt to
|
|
8
|
+
# either `okl record` the lesson or state that there is none. It never fires twice in one
|
|
9
|
+
# session (marker file) and never loops (stop_hook_active guard).
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
# Same resolver as pretooluse-okl-check.sh (env → pinned config → PATH → python3 -m okl);
|
|
13
|
+
# the reminder is best-effort, so an unresolvable okl silently disables it rather than blocking.
|
|
14
|
+
resolve_okl() {
|
|
15
|
+
if [ -n "${OKL_BIN:-}" ]; then printf '%s' "$OKL_BIN"; return 0; fi
|
|
16
|
+
local d="$PWD"
|
|
17
|
+
while [ "$d" != "/" ]; do
|
|
18
|
+
if [ -f "$d/.okl/config.json" ]; then
|
|
19
|
+
local bin
|
|
20
|
+
bin=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("okl_bin") or "")' \
|
|
21
|
+
"$d/.okl/config.json" 2>/dev/null || true)
|
|
22
|
+
if [ -n "$bin" ]; then printf '%s' "$bin"; return 0; fi
|
|
23
|
+
break
|
|
24
|
+
fi
|
|
25
|
+
d=$(dirname "$d")
|
|
26
|
+
done
|
|
27
|
+
if command -v okl >/dev/null 2>&1; then printf '%s' "okl"; return 0; fi
|
|
28
|
+
if python3 -c "import okl" >/dev/null 2>&1; then printf '%s' "python3 -m okl"; return 0; fi
|
|
29
|
+
return 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
OKL=$(resolve_okl) || exit 0
|
|
33
|
+
|
|
34
|
+
payload=$(cat 2>/dev/null || true)
|
|
35
|
+
parsed=$(printf '%s' "$payload" | python3 -c '
|
|
36
|
+
import json, sys
|
|
37
|
+
try:
|
|
38
|
+
d = json.load(sys.stdin)
|
|
39
|
+
except Exception:
|
|
40
|
+
d = {}
|
|
41
|
+
print(d.get("session_id", ""))
|
|
42
|
+
print("true" if d.get("stop_hook_active") else "false")
|
|
43
|
+
' 2>/dev/null) || parsed=""
|
|
44
|
+
session_id=$(printf '%s\n' "$parsed" | sed -n 1p)
|
|
45
|
+
stop_hook_active=$(printf '%s\n' "$parsed" | sed -n 2p)
|
|
46
|
+
[ -n "$stop_hook_active" ] || stop_hook_active="false"
|
|
47
|
+
|
|
48
|
+
# Never loop: if we already blocked once and Claude is stopping again, let it stop.
|
|
49
|
+
[ "${stop_hook_active}" = "true" ] && exit 0
|
|
50
|
+
|
|
51
|
+
# Only fire when the session plausibly did work: uncommitted changes, or a commit in the
|
|
52
|
+
# last hour (covers commit-then-stop sessions).
|
|
53
|
+
changed=0
|
|
54
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
55
|
+
changed=1
|
|
56
|
+
elif last=$(git log -1 --format=%ct 2>/dev/null); then
|
|
57
|
+
now=$(date +%s)
|
|
58
|
+
[ $((now - last)) -lt 3600 ] && changed=1
|
|
59
|
+
fi
|
|
60
|
+
[ "$changed" = "1" ] || exit 0
|
|
61
|
+
|
|
62
|
+
# Once per session (fall back to a repo-scoped marker when no session id is provided).
|
|
63
|
+
marker="${TMPDIR:-/tmp}/okl-encode-reminder-${session_id:-$(pwd | cksum | cut -d' ' -f1)}"
|
|
64
|
+
[ -e "$marker" ] && exit 0
|
|
65
|
+
touch "$marker" 2>/dev/null || true
|
|
66
|
+
|
|
67
|
+
cat >&2 <<'MSG'
|
|
68
|
+
ENCODING LOOP — before this session ends: did it surface a lesson worth keeping?
|
|
69
|
+
A non-obvious failure mode, a rule discovered the hard way, a decision that shouldn't be
|
|
70
|
+
silently reversed? If yes, record it now (choose the scope deliberately — 'org' spreads
|
|
71
|
+
to every repo, 'repo' stays local — and tag the subject):
|
|
72
|
+
|
|
73
|
+
okl record --type Defect|Rule|Decision --scope org|repo --tags "<subjects>" \
|
|
74
|
+
--title "..." --symptom "..." --body "cause: ..." --fix "..."
|
|
75
|
+
|
|
76
|
+
If the session genuinely learned nothing durable, state that explicitly and finish.
|
|
77
|
+
MSG
|
|
78
|
+
exit 2
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# UserPromptSubmit hook — inject the org's relevant lessons into the model's context
|
|
3
|
+
# BEFORE it starts the task. This event is the only correct one for delivery: its stdout
|
|
4
|
+
# (exit 0) is added to Claude's context, and its stdin carries the actual prompt text, so
|
|
5
|
+
# the briefing is retrieved for the task the user really asked for.
|
|
6
|
+
#
|
|
7
|
+
# (The earlier PreToolUse version fired on every edit and printed the briefing to a channel
|
|
8
|
+
# the model never sees — PreToolUse exit-0 stdout goes to the transcript only. Discovered by
|
|
9
|
+
# an end-to-end test: hook fired, briefing correct, defect reproduced anyway.)
|
|
10
|
+
#
|
|
11
|
+
# FAILS CLOSED via exit 2: if the knowledge layer is unreachable, the prompt is blocked
|
|
12
|
+
# rather than letting the agent proceed blind. A check that reports "clean" while broken is
|
|
13
|
+
# worse than no check.
|
|
14
|
+
set -uo pipefail
|
|
15
|
+
|
|
16
|
+
# Resolve how to invoke okl (env → pinned config → PATH → python3 -m okl); hooks run in
|
|
17
|
+
# whatever environment the harness spawns, which often lacks the venv/pipx bin dir.
|
|
18
|
+
resolve_okl() {
|
|
19
|
+
if [ -n "${OKL_BIN:-}" ]; then printf '%s' "$OKL_BIN"; return 0; fi
|
|
20
|
+
local d="$PWD"
|
|
21
|
+
while [ "$d" != "/" ]; do
|
|
22
|
+
if [ -f "$d/.okl/config.json" ]; then
|
|
23
|
+
local bin
|
|
24
|
+
bin=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("okl_bin") or "")' \
|
|
25
|
+
"$d/.okl/config.json" 2>/dev/null || true)
|
|
26
|
+
if [ -n "$bin" ]; then printf '%s' "$bin"; return 0; fi
|
|
27
|
+
break
|
|
28
|
+
fi
|
|
29
|
+
d=$(dirname "$d")
|
|
30
|
+
done
|
|
31
|
+
if command -v okl >/dev/null 2>&1; then printf '%s' "okl"; return 0; fi
|
|
32
|
+
if python3 -c "import okl" >/dev/null 2>&1; then printf '%s' "python3 -m okl"; return 0; fi
|
|
33
|
+
return 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ! OKL=$(resolve_okl); then
|
|
37
|
+
[ "${OKL_OFFLINE:-0}" = "1" ] && exit 0
|
|
38
|
+
echo "okl NOT FOUND — blocking (a check that can't run must not pass as clean)." >&2
|
|
39
|
+
echo "Install it: pip install observed-knowledge-ledger. NOT 'pip install okl' — PyPI refuses that" >&2
|
|
40
|
+
echo "name as confusable, so it fails and looks like the tool does not exist." >&2
|
|
41
|
+
echo "Or set OKL_BIN, or re-run 'okl init' from a shell where okl works (that pins okl_bin" >&2
|
|
42
|
+
echo "into .okl/config.json). OKL_OFFLINE=1 proceeds without the layer." >&2
|
|
43
|
+
exit 2
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# The task is the prompt itself (stdin JSON: {"prompt": "..."}); OKL_TASK overrides;
|
|
47
|
+
# last-commit-message is only the fallback of last resort.
|
|
48
|
+
payload=$(cat 2>/dev/null || true)
|
|
49
|
+
prompt=$(printf '%s' "$payload" | python3 -c '
|
|
50
|
+
import json, sys
|
|
51
|
+
try:
|
|
52
|
+
print((json.load(sys.stdin).get("prompt") or "").strip()[:2000])
|
|
53
|
+
except Exception:
|
|
54
|
+
print("")
|
|
55
|
+
' 2>/dev/null || true)
|
|
56
|
+
TASK="${OKL_TASK:-${prompt:-$(git log -1 --pretty=%s 2>/dev/null || echo 'general work')}}"
|
|
57
|
+
|
|
58
|
+
# $OKL unquoted on purpose: it may be a command + args ("python3 -m okl").
|
|
59
|
+
if out=$($OKL check --task "$TASK" --format agent 2>/dev/null); then
|
|
60
|
+
printf '%s\n' "$out" # stdout → the model's context
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [ "${OKL_OFFLINE:-0}" = "1" ]; then
|
|
65
|
+
echo "OKL offline (OKL_OFFLINE=1 acknowledged) — proceeding without the layer." >&2
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
echo "OKL UNREACHABLE — blocking this prompt. A check that reports 'clean' while broken is worse than no check." >&2
|
|
69
|
+
echo "Fix connectivity, or set OKL_OFFLINE=1 to explicitly proceed without the org knowledge layer." >&2
|
|
70
|
+
exit 2
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"Stop": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-okl-encode.sh"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"UserPromptSubmit": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/userpromptsubmit-okl-check.sh"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something behaves differently than documented
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**What you ran** (the exact command, and `okl --help` version if relevant)
|
|
8
|
+
|
|
9
|
+
**What you expected**
|
|
10
|
+
|
|
11
|
+
**What actually happened** (paste the real output, not a summary)
|
|
12
|
+
|
|
13
|
+
**Environment**
|
|
14
|
+
- OS:
|
|
15
|
+
- Python:
|
|
16
|
+
- Installed via: `pip install observed-knowledge-ledger` / `pip install -e .`
|
|
17
|
+
- Mode: local store / shared service
|
|
18
|
+
|
|
19
|
+
**Especially useful:** if the tool reported success it had not earned (a check that
|
|
20
|
+
passed without running, a stamp without evidence, a hook that fired but changed
|
|
21
|
+
nothing), say so explicitly. That class of defect is this project's subject.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Proposal
|
|
3
|
+
about: Suggest a change to how the loop works
|
|
4
|
+
labels: proposal
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**The symptom you hit** (what went wrong in real use, not the feature you want)
|
|
8
|
+
|
|
9
|
+
**Why the current design does not cover it**
|
|
10
|
+
|
|
11
|
+
**What you would change**
|
|
12
|
+
|
|
13
|
+
**How you would know it worked** — the check, measurement, or test that would show it.
|
|
14
|
+
Proposals that name their own evidence get adopted faster; see `evals/REPORT.md` for
|
|
15
|
+
the standard this repo holds itself to.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Dependabot.
|
|
2
|
+
#
|
|
3
|
+
# This exists because of the SHA pinning, not despite it. Pinning actions to a commit
|
|
4
|
+
# closes the "a mutable tag moved under me" hole and opens a quieter one: the pin never
|
|
5
|
+
# updates, so a security fix in an action never arrives. Pinning without an update path
|
|
6
|
+
# is how a repo ends up on a two-year-old checkout action and calls it hardening.
|
|
7
|
+
# Dependabot rewrites the SHA and the trailing `# v5` comment together.
|
|
8
|
+
version: 2
|
|
9
|
+
|
|
10
|
+
updates:
|
|
11
|
+
- package-ecosystem: github-actions
|
|
12
|
+
directory: "/"
|
|
13
|
+
schedule:
|
|
14
|
+
interval: weekly
|
|
15
|
+
labels: ["dependencies", "ci"]
|
|
16
|
+
commit-message:
|
|
17
|
+
prefix: "ci"
|
|
18
|
+
|
|
19
|
+
- package-ecosystem: pip
|
|
20
|
+
directory: "/"
|
|
21
|
+
schedule:
|
|
22
|
+
interval: weekly
|
|
23
|
+
labels: ["dependencies"]
|
|
24
|
+
commit-message:
|
|
25
|
+
prefix: "deps"
|
|
26
|
+
# The gating tools are pinned exactly on purpose (a range lets a new release
|
|
27
|
+
# retro-fail a branch that changed nothing). Dependabot is the intended way to move
|
|
28
|
+
# those pins: a PR that runs the full suite against the new version before it lands,
|
|
29
|
+
# rather than a range that upgrades silently mid-branch.
|
|
30
|
+
groups:
|
|
31
|
+
dev-tooling:
|
|
32
|
+
patterns: ["pytest", "ruff", "mypy"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## What changed
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
## What you ran to check it
|
|
6
|
+
|
|
7
|
+
<!-- Paste real output. `pytest -q`, `ruff check .`, and `okl drift` at minimum. -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] `pytest -q` green
|
|
12
|
+
- [ ] `ruff check .` clean
|
|
13
|
+
- [ ] `okl drift` green, or affected records re-verified with `okl verify --run`
|
|
14
|
+
- [ ] Mirror files still byte-identical (`ci/`, `.github/workflows/`, `hooks/` and their `src/okl/scaffold/` twins)
|
|
15
|
+
- [ ] Any number quoted has a committed receipt in `evals/results/`
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Repo CI — lints, type-checks and tests okl itself. (okl-verify.yml is different: it
|
|
2
|
+
# demonstrates the product's own drift/method gates, the workflow consumers copy.)
|
|
3
|
+
#
|
|
4
|
+
# Hardened per the org rule this repo's own store carries ("CI workflow baseline:
|
|
5
|
+
# pipefail, no persisted credentials, least-privilege permissions, concurrency groups —
|
|
6
|
+
# pin actions"). It satisfied none of the five until this commit, which is the rule's own
|
|
7
|
+
# symptom: an enforcement surface nothing triggers runs never.
|
|
8
|
+
name: ci
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [ "main" ]
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: [ "main" ]
|
|
15
|
+
|
|
16
|
+
# Least privilege: this job reads code and reports status. It never pushes.
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
# One run per ref. A superseded push should stop burning minutes on a result nobody
|
|
21
|
+
# will read.
|
|
22
|
+
concurrency:
|
|
23
|
+
group: ci-${{ github.ref }}
|
|
24
|
+
cancel-in-progress: true
|
|
25
|
+
|
|
26
|
+
defaults:
|
|
27
|
+
run:
|
|
28
|
+
# Without pipefail a run block continues past a failed segment of a pipeline, so a
|
|
29
|
+
# green step can hide a red command.
|
|
30
|
+
shell: bash -euo pipefail {0}
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
lint-and-test:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
# Actions pinned to a commit SHA, not a tag: a tag is mutable, so `@v5` is an
|
|
37
|
+
# unpinned dependency with write access to the runner.
|
|
38
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
39
|
+
with:
|
|
40
|
+
# The diagram gate reads committed history (git show HEAD:...), not the
|
|
41
|
+
# checkout tree, so it needs more than the default shallow fetch.
|
|
42
|
+
fetch-depth: 0
|
|
43
|
+
# This job never pushes; leaving the token in .git/config lets any later step
|
|
44
|
+
# (or a compromised dependency) use it.
|
|
45
|
+
persist-credentials: false
|
|
46
|
+
|
|
47
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
|
|
51
|
+
- name: Install (dev extras)
|
|
52
|
+
run: pip install -e ".[dev]"
|
|
53
|
+
|
|
54
|
+
- name: Lint — ruff (config in pyproject.toml)
|
|
55
|
+
run: ruff check .
|
|
56
|
+
|
|
57
|
+
- name: Types — mypy
|
|
58
|
+
run: mypy src/okl
|
|
59
|
+
|
|
60
|
+
- name: Tests (with the coverage ratchet)
|
|
61
|
+
run: pytest -q --cov=okl
|
|
62
|
+
|
|
63
|
+
- name: Diagram figures trace to committed receipts
|
|
64
|
+
run: ./ci/check-diagram-figures.sh
|
|
65
|
+
|
|
66
|
+
# The content audits okl ships to consumers, run against okl itself. They were
|
|
67
|
+
# shipped for weeks without this repo running any of them — the "enforcement
|
|
68
|
+
# surface nothing triggers" failure, one level up: a gate whose author has never
|
|
69
|
+
# run it on their own tree.
|
|
70
|
+
- name: Method gates (links, doc orphans, diagram pairs, tombstones, canon size)
|
|
71
|
+
run: bash gates/run-gates.sh
|
|
72
|
+
|
|
73
|
+
# Secret scan over the FULL history, not the working tree. okl writes a bearer
|
|
74
|
+
# token into .okl/config.json, and the .gitignore it now drops there is the only
|
|
75
|
+
# thing between that and a commit — this is the check for when that fails.
|
|
76
|
+
#
|
|
77
|
+
# The upstream gitleaks-action is deliberately not used: it requires a paid licence
|
|
78
|
+
# key for organization-owned repositories, so it would break for anyone who forks
|
|
79
|
+
# this into an org. The binary has no such condition, and pinning the release keeps
|
|
80
|
+
# a new detection rule from retro-failing a branch that changed nothing.
|
|
81
|
+
# The reviewer okl ships, run on okl. It was listed as a review surface with nothing
|
|
82
|
+
# triggering it — the "enforcement surface that only runs on-demand runs never"
|
|
83
|
+
# failure this repo's own store names. Off unless REVIEW_CMD is set, so it costs
|
|
84
|
+
# nothing until someone decides it should.
|
|
85
|
+
- name: Architecture review (off unless REVIEW_CMD is set)
|
|
86
|
+
if: github.event_name == 'pull_request'
|
|
87
|
+
env:
|
|
88
|
+
REVIEW_CMD: ${{ vars.REVIEW_CMD }}
|
|
89
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
90
|
+
REVIEW_BASE_REF: origin/${{ github.base_ref }}
|
|
91
|
+
run: bash ci/review-agent.sh
|
|
92
|
+
|
|
93
|
+
- name: Secret scan — gitleaks
|
|
94
|
+
env:
|
|
95
|
+
GITLEAKS_VERSION: "8.30.1"
|
|
96
|
+
run: |
|
|
97
|
+
curl -sSfL -o /tmp/gitleaks.tar.gz \
|
|
98
|
+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
99
|
+
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
|
|
100
|
+
/tmp/gitleaks git . --no-banner --redact --exit-code 1
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Installed by `okl init` into .github/workflows/okl-verify.yml (also stamped by `okl scaffold`).
|
|
2
|
+
#
|
|
3
|
+
# The knowledge layer's CI verifier — real checks, not placeholders:
|
|
4
|
+
# 1. `okl drift --gate` — fails the build when a rule's governed files changed after the
|
|
5
|
+
# rule was last verified (a stale rule is a rule nobody re-checked).
|
|
6
|
+
# 2. `gates/run-gates.sh` — the repo's mechanical method gates, when the scaffold is present.
|
|
7
|
+
#
|
|
8
|
+
# Shared-layer connection is optional: without OKL_SERVICE_URL the drift gate reads the
|
|
9
|
+
# committed snapshot okl-drift.json (`okl export --drift`; `okl verify` keeps it current),
|
|
10
|
+
# which also covers fork PRs, since they get no secrets. Configure the secrets to verify against the org layer and to
|
|
11
|
+
# let gate scripts emit receipts (`okl link <gate_id> VERIFIED_ON <defect_id>`) — emit those
|
|
12
|
+
# from inside the gate that proved itself against real drift, where the ids are known.
|
|
13
|
+
name: okl-verify
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
pull_request:
|
|
17
|
+
push:
|
|
18
|
+
branches: [main]
|
|
19
|
+
|
|
20
|
+
# Least privilege: this workflow reads code and reports a status. It never writes.
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: okl-verify-${{ github.ref }}
|
|
26
|
+
cancel-in-progress: true
|
|
27
|
+
|
|
28
|
+
defaults:
|
|
29
|
+
run:
|
|
30
|
+
# Without pipefail a run block continues past a failed segment of a pipeline, so a
|
|
31
|
+
# green step can hide a red command.
|
|
32
|
+
shell: bash -euo pipefail {0}
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
okl-verify:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
env:
|
|
38
|
+
# Set at job level so every okl command below resolves the shared layer from the
|
|
39
|
+
# environment. This replaces an `okl connect --token` step, which wrote the bearer
|
|
40
|
+
# token into .okl/config.json on the runner — needless, since the client reads both
|
|
41
|
+
# variables directly, and one more place for a credential to end up.
|
|
42
|
+
# Unset secrets leave these empty, and okl falls back to the repo-local store.
|
|
43
|
+
OKL_SERVICE_URL: ${{ secrets.OKL_SERVICE_URL }}
|
|
44
|
+
OKL_TOKEN: ${{ secrets.OKL_TOKEN }}
|
|
45
|
+
steps:
|
|
46
|
+
# Actions are pinned to a commit SHA: a tag is mutable, so `@v5` is an unpinned
|
|
47
|
+
# dependency with access to your runner. If you copy this file into your own repo,
|
|
48
|
+
# add a .github/dependabot.yml with the `github-actions` ecosystem so these pins get
|
|
49
|
+
# security updates — a pin with no update path goes stale and that is its own risk.
|
|
50
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
51
|
+
with:
|
|
52
|
+
fetch-depth: 0 # drift needs history: it compares governed-file commits vs verified_at
|
|
53
|
+
persist-credentials: false # this job never pushes
|
|
54
|
+
|
|
55
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.13"
|
|
58
|
+
|
|
59
|
+
- name: Install okl
|
|
60
|
+
# Detect by the package source, not the distribution name: the name changed once
|
|
61
|
+
# (PyPI rejects "okl" as confusable) and a name-based check silently broke with it.
|
|
62
|
+
run: |
|
|
63
|
+
if [ -f src/okl/cli.py ]; then
|
|
64
|
+
pip install -e .
|
|
65
|
+
else
|
|
66
|
+
pip install observed-knowledge-ledger
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
- name: Report which store the gate will run against
|
|
70
|
+
run: |
|
|
71
|
+
if [ -n "${OKL_SERVICE_URL:-}" ]; then
|
|
72
|
+
echo "verifying against the shared layer at $OKL_SERVICE_URL"
|
|
73
|
+
elif git ls-files --error-unmatch okl-drift.json >/dev/null 2>&1; then
|
|
74
|
+
echo "no OKL_SERVICE_URL secret — verifying against the committed okl-drift.json"
|
|
75
|
+
else
|
|
76
|
+
echo "no OKL_SERVICE_URL secret and no committed okl-drift.json — nothing to verify against"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
- name: Drift gate — rules whose governed code changed after last verification
|
|
80
|
+
# The exit code is the verdict: 0 checked and clean, 1 drift found, 2 did not run.
|
|
81
|
+
# "Did not run" is the case this step used to report as a pass: with no store in CI
|
|
82
|
+
# (okl init gitignores it) the gate checked nothing and printed OK. It now says so,
|
|
83
|
+
# as a warning annotation on the PR rather than a failure, because an unseeded
|
|
84
|
+
# store is a setup gap, not a defect in the change under review -- and failing on it
|
|
85
|
+
# would block every pull request from the moment okl is installed. To make this
|
|
86
|
+
# step a real check, give CI a store: commit `okl export --drift`, or set the
|
|
87
|
+
# OKL_SERVICE_URL secret. A committed snapshot IS a store for this purpose, so once
|
|
88
|
+
# one exists, exit 2 fails like any other configured store.
|
|
89
|
+
run: |
|
|
90
|
+
snapshot=""
|
|
91
|
+
if [ -z "${OKL_SERVICE_URL:-}${OKL_DATABASE_URL:-}" ] \
|
|
92
|
+
&& git ls-files --error-unmatch okl-drift.json >/dev/null 2>&1; then
|
|
93
|
+
snapshot="okl-drift.json"
|
|
94
|
+
fi
|
|
95
|
+
set +e
|
|
96
|
+
if [ -n "$snapshot" ]; then
|
|
97
|
+
okl drift --gate --snapshot "$snapshot"
|
|
98
|
+
else
|
|
99
|
+
okl drift --gate
|
|
100
|
+
fi
|
|
101
|
+
rc=$?
|
|
102
|
+
set -e
|
|
103
|
+
# Exit 2 means "did not run", which has two causes that deserve opposite outcomes.
|
|
104
|
+
# If this job was never given a store, it is the setup gap above: warn. If a store
|
|
105
|
+
# WAS configured and okl still did not run -- unreachable, refused, or holding no
|
|
106
|
+
# rule for this repo -- something broke, and passing would hide an outage behind
|
|
107
|
+
# a green check. The job knows which it is; okl's exit code alone cannot say.
|
|
108
|
+
if [ "$rc" -eq 2 ] && [ -z "${OKL_SERVICE_URL:-}${OKL_DATABASE_URL:-}${snapshot}" ]; then
|
|
109
|
+
echo "::warning title=Drift not checked::No knowledge store is available to this job, so no rule was checked for drift. This step passing is NOT an all-clear. Commit \`okl export --drift\` to give CI one."
|
|
110
|
+
elif [ "$rc" -ne 0 ]; then
|
|
111
|
+
exit "$rc"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
- name: Method gates (when the scaffold kit is present)
|
|
115
|
+
run: |
|
|
116
|
+
if [ -x gates/run-gates.sh ]; then
|
|
117
|
+
bash gates/run-gates.sh
|
|
118
|
+
else
|
|
119
|
+
echo "no gates/run-gates.sh — method-kit gates not installed (okl scaffold adds them)"
|
|
120
|
+
fi
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Release to PyPI on a version tag, using Trusted Publishing.
|
|
2
|
+
#
|
|
3
|
+
# There is no API token anywhere in this workflow, and none stored in the repository.
|
|
4
|
+
# PyPI accepts the upload because GitHub vouches for it: the publish job mints a
|
|
5
|
+
# short-lived OIDC token that names this repository, this workflow file and this
|
|
6
|
+
# environment, and PyPI checks that against the trusted publisher it was told to expect.
|
|
7
|
+
# A leaked repository secret cannot publish okl because there is no such secret.
|
|
8
|
+
#
|
|
9
|
+
# ONE-TIME SETUP ON PYPI (this workflow does nothing until it is done). The project was
|
|
10
|
+
# renamed from org-knowledge-layer in 0.6.0: the NEW name needs its own pending publisher
|
|
11
|
+
# below, and the old project keeps the one it already has (it publishes the redirect):
|
|
12
|
+
# pypi.org → the observed-knowledge-ledger project → Publishing → Add a pending publisher
|
|
13
|
+
# Owner: emeraldleaf
|
|
14
|
+
# Repository: okl
|
|
15
|
+
# Workflow name: publish.yml
|
|
16
|
+
# Environment name: pypi
|
|
17
|
+
# Then create the `pypi` environment under repo Settings → Environments. Adding a
|
|
18
|
+
# required reviewer there makes every release a deliberate, approved act.
|
|
19
|
+
name: publish
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
tags: ["v*"]
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
build:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.12"
|
|
39
|
+
|
|
40
|
+
# The tag is the release's claim about what version this is; pyproject.toml is the
|
|
41
|
+
# version that actually gets uploaded. When they disagree, PyPI takes pyproject's
|
|
42
|
+
# answer and the tag becomes a lie that is permanent — you cannot reuse a version
|
|
43
|
+
# number on PyPI, so the mistake is not fixable, only worked around.
|
|
44
|
+
- name: Tag must match the version in pyproject.toml
|
|
45
|
+
run: |
|
|
46
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
47
|
+
pkg="$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")"
|
|
48
|
+
echo "tag=$tag pyproject=$pkg"
|
|
49
|
+
if [ "$tag" != "$pkg" ]; then
|
|
50
|
+
echo "::error::tag v$tag does not match pyproject version $pkg"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
- name: Build
|
|
55
|
+
run: |
|
|
56
|
+
python -m pip install --upgrade build twine
|
|
57
|
+
python -m build
|
|
58
|
+
# The old distribution name's final release (packaging/org-knowledge-layer):
|
|
59
|
+
# built only on the tag that matches its version, so it is published exactly
|
|
60
|
+
# once. Its PyPI project already trusts this workflow.
|
|
61
|
+
shim="$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('packaging/org-knowledge-layer/pyproject.toml').read_text())['project']['version'])")"
|
|
62
|
+
if [ "${GITHUB_REF_NAME#v}" = "$shim" ]; then
|
|
63
|
+
python -m build packaging/org-knowledge-layer --outdir dist/
|
|
64
|
+
fi
|
|
65
|
+
# `twine check` catches a malformed long_description, which PyPI rejects only
|
|
66
|
+
# after the upload has otherwise succeeded.
|
|
67
|
+
python -m twine check dist/*
|
|
68
|
+
|
|
69
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
|
|
74
|
+
publish:
|
|
75
|
+
needs: build
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
# A named environment is what the PyPI trusted publisher is bound to, and it is where
|
|
78
|
+
# a required-reviewer rule can gate the release.
|
|
79
|
+
environment:
|
|
80
|
+
name: pypi
|
|
81
|
+
url: https://pypi.org/project/observed-knowledge-ledger/
|
|
82
|
+
permissions:
|
|
83
|
+
id-token: write # mint the OIDC token PyPI verifies
|
|
84
|
+
attestations: write # sign the artifacts
|
|
85
|
+
contents: read
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
88
|
+
with:
|
|
89
|
+
name: dist
|
|
90
|
+
path: dist/
|
|
91
|
+
|
|
92
|
+
# Provenance: a signed statement that these exact files were built by this workflow
|
|
93
|
+
# from this commit. It is what lets someone verify the wheel on PyPI came from the
|
|
94
|
+
# source they are reading, rather than trusting that it did.
|
|
95
|
+
- uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
96
|
+
with:
|
|
97
|
+
subject-path: "dist/*"
|
|
98
|
+
|
|
99
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|