strata-agent-memory 0.2.3__tar.gz → 0.2.4__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.
- {strata_agent_memory-0.2.3/strata_agent_memory.egg-info → strata_agent_memory-0.2.4}/PKG-INFO +2 -1
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/README.md +1 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/__init__.py +1 -1
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/approvals.py +22 -2
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/audit.py +112 -6
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/cli.py +266 -29
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/compiler.py +169 -8
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/contracts.py +2 -1
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/evidence.py +558 -29
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/export.py +2 -1
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/hooks.py +21 -2
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/instances.py +4 -4
- strata_agent_memory-0.2.4/agent_memory/kit.py +256 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/ledger.py +374 -66
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/mcp_server.py +18 -3
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/pathmap.py +78 -5
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/relay/adapters.py +95 -17
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/relay/cli.py +90 -16
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/relay/client.py +167 -28
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/relay/core.py +222 -33
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/remote.py +47 -12
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/render.py +97 -21
- strata_agent_memory-0.2.4/agent_memory/roster.py +277 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/serve.py +56 -8
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/README.md +94 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/onboard-agent.sh +5 -3
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/relay-agent.sh +9 -14
- strata_agent_memory-0.2.4/deploy/plugins/.claude-plugin/marketplace.json +12 -0
- strata_agent_memory-0.2.4/deploy/plugins/README.md +112 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/.claude-plugin/plugin.json +8 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/bin/agent-memory-approvals +235 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/hooks/hooks.json +16 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/rules/approvals.md +6 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/skills/approvals/SKILL.md +23 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/skills/approve/SKILL.md +25 -0
- strata_agent_memory-0.2.4/deploy/plugins/claude-code/skills/reject/SKILL.md +25 -0
- strata_agent_memory-0.2.4/deploy/plugins/codex/skills/agent-memory-answer/SKILL.md +22 -0
- strata_agent_memory-0.2.4/deploy/plugins/codex/skills/agent-memory-answer/agents/openai.yaml +6 -0
- strata_agent_memory-0.2.4/deploy/plugins/codex/skills/agent-memory-approvals/SKILL.md +20 -0
- strata_agent_memory-0.2.4/deploy/plugins/codex/skills/agent-memory-approvals/agents/openai.yaml +6 -0
- strata_agent_memory-0.2.4/deploy/plugins/codex/skills/agent-memory-approvals/rules/approvals.md +6 -0
- strata_agent_memory-0.2.4/deploy/plugins/codex/skills/agent-memory-approvals/scripts/agent-memory-approvals +235 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/relay/install-relay-mac.sh +40 -5
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/relay/install-relay.sh +6 -3
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/relay/macos/com.strata.agent-memory.relay.plist.template +5 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/pyproject.toml +16 -2
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4/strata_agent_memory.egg-info}/PKG-INFO +2 -1
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/strata_agent_memory.egg-info/SOURCES.txt +21 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_audit.py +116 -0
- strata_agent_memory-0.2.4/tests/test_changelog_fragments.py +138 -0
- strata_agent_memory-0.2.4/tests/test_cli.py +526 -0
- strata_agent_memory-0.2.4/tests/test_compiler.py +187 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_compiler_render.py +208 -2
- strata_agent_memory-0.2.4/tests/test_config.py +296 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_contracts.py +27 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_deploy_mac.py +343 -4
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_deploy_relay.py +86 -2
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_deploy_tokens.py +52 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_evidence.py +364 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_hooks.py +33 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_packaging.py +17 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_pathmap.py +78 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_phase2.py +43 -1
- strata_agent_memory-0.2.4/tests/test_plugins.py +437 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_records.py +39 -0
- strata_agent_memory-0.2.4/tests/test_relay_adapters.py +316 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_relay_client.py +479 -13
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_relay_core.py +264 -1
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_remote.py +197 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_serve.py +83 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_trust.py +378 -0
- strata_agent_memory-0.2.3/agent_memory/kit.py +0 -94
- strata_agent_memory-0.2.3/tests/test_cli.py +0 -285
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/LICENSE +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/__main__.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/compact.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/identity.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/memorizer.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/recheck.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/records.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/relay/__init__.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/reliability.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/signing.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/stats.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/agent_memory/trust.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/agent-keys.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/agent-session.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/backup-tokens.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/install-hooks-mac.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/install-relay-driver.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/install-replica-driver.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/install-runner-driver.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/instances.example.json +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/seed-board.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/seed-mail-context.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/seed-topics.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/mac/upgrade-driver.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/relay/README.md +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/relay/agent-memory-relay@.service +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/deploy/relay/relay-launch.sh +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/setup.cfg +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/strata_agent_memory.egg-info/dependency_links.txt +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/strata_agent_memory.egg-info/entry_points.txt +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/strata_agent_memory.egg-info/requires.txt +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/strata_agent_memory.egg-info/top_level.txt +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_backlog_check.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_compact.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_deploy_replica.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_identity.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_issue_bridge.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_ledger.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_ledger_index.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_memorizer.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_recheck.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_redaction.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_relay_codex.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_reliability.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_remote_signing.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_review2_fixes.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_review3_fixes.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_review_fixes.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_scale.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_signing.py +0 -0
- {strata_agent_memory-0.2.3 → strata_agent_memory-0.2.4}/tests/test_stats.py +0 -0
{strata_agent_memory-0.2.3/strata_agent_memory.egg-info → strata_agent_memory-0.2.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: strata-agent-memory
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Portable, auditable, cross-vendor memory for LLM agents: evidence-bound claims, trust tiers, and compiled context views.
|
|
5
5
|
Author: Strata Intelligence
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -201,6 +201,7 @@ tests/ pytest suite; tests/sit/phase3/ is the CLI-driven system test
|
|
|
201
201
|
- [docs/RESEARCH-SYNTHESIS.md](docs/RESEARCH-SYNTHESIS.md): the nine sources, what was taken from each, and what none of them had.
|
|
202
202
|
- [docs/INTEGRATION.md](docs/INTEGRATION.md): setup per harness, the local verifier lane, the contract loop.
|
|
203
203
|
- [docs/COMMS-BUS-PLAN.md](docs/COMMS-BUS-PLAN.md): how agents talk through the ledger without spending tokens while they wait, and [docs/RUNBOOK-RELAY.md](docs/RUNBOOK-RELAY.md) to run a relay per agent.
|
|
204
|
+
- [docs/HARNESS-MATRIX.md](docs/HARNESS-MATRIX.md): the M3 harness reach matrix: MCP, hooks, wake paths, compile injection and relay fitness for every harness surface, from vendor documentation (nothing run).
|
|
204
205
|
- [docs/LANDSCAPE.md](docs/LANDSCAPE.md): the competing systems, their verified licenses, the standards receipts align with, and what none of them do.
|
|
205
206
|
- [docs/UAT-PLAN.md](docs/UAT-PLAN.md): the one-week acceptance test across Claude Code, Codex, Antigravity and Grok.
|
|
206
207
|
- [docs/UAT-SELF.md](docs/UAT-SELF.md): the same test for one person with two subscriptions, Claude Code and Codex CLI, with `scripts/uat/` to set it up and to run the cross-lineage verification.
|
|
@@ -177,6 +177,7 @@ tests/ pytest suite; tests/sit/phase3/ is the CLI-driven system test
|
|
|
177
177
|
- [docs/RESEARCH-SYNTHESIS.md](docs/RESEARCH-SYNTHESIS.md): the nine sources, what was taken from each, and what none of them had.
|
|
178
178
|
- [docs/INTEGRATION.md](docs/INTEGRATION.md): setup per harness, the local verifier lane, the contract loop.
|
|
179
179
|
- [docs/COMMS-BUS-PLAN.md](docs/COMMS-BUS-PLAN.md): how agents talk through the ledger without spending tokens while they wait, and [docs/RUNBOOK-RELAY.md](docs/RUNBOOK-RELAY.md) to run a relay per agent.
|
|
180
|
+
- [docs/HARNESS-MATRIX.md](docs/HARNESS-MATRIX.md): the M3 harness reach matrix: MCP, hooks, wake paths, compile injection and relay fitness for every harness surface, from vendor documentation (nothing run).
|
|
180
181
|
- [docs/LANDSCAPE.md](docs/LANDSCAPE.md): the competing systems, their verified licenses, the standards receipts align with, and what none of them do.
|
|
181
182
|
- [docs/UAT-PLAN.md](docs/UAT-PLAN.md): the one-week acceptance test across Claude Code, Codex, Antigravity and Grok.
|
|
182
183
|
- [docs/UAT-SELF.md](docs/UAT-SELF.md): the same test for one person with two subscriptions, Claude Code and Codex CLI, with `scripts/uat/` to set it up and to run the cross-lineage verification.
|
|
@@ -37,6 +37,7 @@ import re
|
|
|
37
37
|
from typing import Iterable, List, Optional
|
|
38
38
|
|
|
39
39
|
from . import contracts
|
|
40
|
+
from .relay.core import agent_name
|
|
40
41
|
from .records import ValidationError, new_record, scope_for_target, sort_key
|
|
41
42
|
|
|
42
43
|
GATE_KINDS = ("close", "retraction", "reopen", "reject", "constraint", "needs-human")
|
|
@@ -80,6 +81,17 @@ def _who(author: dict) -> str:
|
|
|
80
81
|
return f"{author.get('harness') or author.get('kind') or '?'}/{author.get('user') or '?'}"
|
|
81
82
|
|
|
82
83
|
|
|
84
|
+
def _on_thread(ledger, thread: str, author: dict) -> bool:
|
|
85
|
+
"""Whether a note's author is on the thread its tag names: the root's author or one of
|
|
86
|
+
its addressees, as the relay that stopped the thread is."""
|
|
87
|
+
root = ledger.get(thread)
|
|
88
|
+
if not isinstance(root, dict):
|
|
89
|
+
return False
|
|
90
|
+
names = {agent_name(root.get("author") or {})}
|
|
91
|
+
names.update(t[len("to:"):] for t in _tags(root) if t.startswith("to:"))
|
|
92
|
+
return agent_name(author) in names
|
|
93
|
+
|
|
94
|
+
|
|
83
95
|
def _answered(ledger) -> tuple:
|
|
84
96
|
"""({item id: the newest human answer's ts}, {thread: the newest human record's ts}).
|
|
85
97
|
|
|
@@ -266,12 +278,20 @@ def pending(ledger, assessment=None, reader: Optional[str] = None) -> dict:
|
|
|
266
278
|
if "needs-human" in tags: # an alarm in either mode: auto removes approvals, not alarms
|
|
267
279
|
thread = next((t[len("thread:"):] for t in tags if t.startswith("thread:")), None)
|
|
268
280
|
if thread and released.get(thread, "") <= (note.get("ts") or ""):
|
|
281
|
+
# a relay's note on a stop something released before the note was due (a
|
|
282
|
+
# contract update, section 11) is news for a person, and waits on nobody --
|
|
283
|
+
# when the relay that stopped is the one saying so: any token can file a note,
|
|
284
|
+
# and one tagged released by a stranger to the thread is still a stop
|
|
285
|
+
gone = "released" in tags and _on_thread(ledger, thread, note.get("author") or {})
|
|
269
286
|
items.append(_item(ledger, "needs-human", note["id"], (note.get("body") or {}).get("contract"),
|
|
270
287
|
f"thread {thread}", _short(note["body"].get("statement")),
|
|
271
288
|
f"evidence: the last message on it is {', '.join(note.get('derived_from') or []) or '?'}",
|
|
272
289
|
{"verdict": None, "id": None, "line": "review: none (a breaker, not work)"},
|
|
273
|
-
{"id": None, "line": "risk: the thread
|
|
274
|
-
|
|
290
|
+
{"id": None, "line": ("risk: none -- the thread was released before this note, "
|
|
291
|
+
"and what it held goes to the agent with the release")
|
|
292
|
+
if gone else
|
|
293
|
+
"risk: the thread stays stopped until a person answers on it"},
|
|
294
|
+
"approve", gate=not gone, thread=thread))
|
|
275
295
|
continue
|
|
276
296
|
kind = next((t[len("approval:"):] for t in tags if t.startswith("approval:")), None)
|
|
277
297
|
if kind not in kinds:
|
|
@@ -28,8 +28,10 @@ from typing import Dict, List, Optional
|
|
|
28
28
|
|
|
29
29
|
from . import evidence as ev
|
|
30
30
|
from . import reliability as rel
|
|
31
|
+
from . import roster as roster_mod
|
|
31
32
|
from . import signing
|
|
32
33
|
from .contracts import status_of
|
|
34
|
+
from .ledger import UNTRUSTED_POLICY
|
|
33
35
|
from .records import is_redacted, receipt_visible_to
|
|
34
36
|
from .trust import TIERS, assess, lineage_differs, summarize
|
|
35
37
|
|
|
@@ -118,6 +120,74 @@ def _capped(items: List[dict]) -> dict:
|
|
|
118
120
|
return {"total": len(items), "items": newest[:_SECTION_CAP]}
|
|
119
121
|
|
|
120
122
|
|
|
123
|
+
# a line break, or any other control character, in a field a record carries: the report is
|
|
124
|
+
# read by people and, through memory_audit, by models, and one row must stay one row
|
|
125
|
+
_BREAKS = re.compile(r"[\x00-\x1f\x7f-\x9f\u2028\u2029]+")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _one_line(text: str) -> str:
|
|
129
|
+
"""A report row flattened to one line: a harness or a user is whatever the filing
|
|
130
|
+
process said it was, and a newline in one would forge a heading in the report."""
|
|
131
|
+
return _BREAKS.sub(" ", str(text))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
ROSTER_OUTSIDERS = ("people and the ci lineage are on no agent roster by design and are not listed; a "
|
|
135
|
+
"person's or ci's record stays what it is (docs/PM-BRIEF.md step 4)")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _roster_sections(ledger, rep: Dict, by_id: Dict[str, dict], reader_user: Optional[str]) -> None:
|
|
139
|
+
"""The three questions the roster answers for a corpus (docs/PROTOCOL.md section 12):
|
|
140
|
+
who filed without being on it, who filed after being retired from it, and who
|
|
141
|
+
verified a claim of a project whose roster they are not on.
|
|
142
|
+
|
|
143
|
+
None of them moves a tier or refuses anything: each is the question to ask before the
|
|
144
|
+
lineage sections above are read as facts. A ledger with no roster answers none of
|
|
145
|
+
them, and says so rather than listing everybody.
|
|
146
|
+
"""
|
|
147
|
+
config = ledger.config
|
|
148
|
+
roster = roster_mod.entries(config)
|
|
149
|
+
on_roster = roster_mod.by_user(config)
|
|
150
|
+
has_roster = bool(roster)
|
|
151
|
+
# every lookup the loop makes, built once: per record they would rebuild the roster, and
|
|
152
|
+
# an audit is records x roster that way
|
|
153
|
+
retired = {roster_mod.user_key(e.get("user")): e["retired"] for e in roster.values()
|
|
154
|
+
if isinstance(e, dict) and e.get("retired")}
|
|
155
|
+
project_users: Dict[str, set] = {}
|
|
156
|
+
for e in roster.values():
|
|
157
|
+
for project in (e.get("projects") or []) if isinstance(e, dict) else ():
|
|
158
|
+
project_users.setdefault(project, set()).add(roster_mod.user_key(e.get("user")))
|
|
159
|
+
has_projects = bool(project_users)
|
|
160
|
+
off, late, outside = [], [], []
|
|
161
|
+
for rec in ledger.all() if has_roster else ():
|
|
162
|
+
if rec["kind"] not in _IDENTITY_KINDS or not _reader_sees(ledger, rec, by_id, reader_user):
|
|
163
|
+
continue
|
|
164
|
+
a = rec["author"]
|
|
165
|
+
if a.get("kind") == "human" or a.get("harness") == "ci":
|
|
166
|
+
continue
|
|
167
|
+
row = {"id": rec["id"], "kind": rec["kind"], "user": a.get("user"), "harness": a.get("harness"),
|
|
168
|
+
"ts": rec["ts"]}
|
|
169
|
+
key = roster_mod.user_key(a.get("user"))
|
|
170
|
+
if key not in on_roster:
|
|
171
|
+
off.append(row)
|
|
172
|
+
elif key in retired and rec["ts"][:10] > retired[key]: # after the retired date, not on it
|
|
173
|
+
late.append(dict(row, retired=retired[key]))
|
|
174
|
+
if rec["kind"] == "verification": # an off-roster verifier is outside the project's roster too
|
|
175
|
+
claim = ledger.get((rec.get("body") or {}).get("claim"))
|
|
176
|
+
project = ((claim or {}).get("scope") or {}).get("project")
|
|
177
|
+
users = project_users.get(project) # None: the roster says nothing about this project
|
|
178
|
+
if claim is not None and users is not None and key not in users:
|
|
179
|
+
outside.append(dict(row, claim=claim["id"], project=project,
|
|
180
|
+
verdict=(rec.get("body") or {}).get("verdict")))
|
|
181
|
+
rep["records_off_roster"] = _capped(off)
|
|
182
|
+
rep["records_after_retirement"] = _capped(late)
|
|
183
|
+
rep["verifications_outside_project_roster"] = _capped(outside)
|
|
184
|
+
for key in ("records_off_roster", "records_after_retirement", "verifications_outside_project_roster"):
|
|
185
|
+
if not has_roster:
|
|
186
|
+
rep[key]["no_roster"] = True
|
|
187
|
+
if has_roster and not has_projects:
|
|
188
|
+
rep["verifications_outside_project_roster"]["no_projects"] = True
|
|
189
|
+
|
|
190
|
+
|
|
121
191
|
def report(ledger, recheck: bool = False, cwd: Optional[str] = None, timeout: Optional[float] = 300,
|
|
122
192
|
path_map=None, reader_user: Optional[str] = None) -> dict:
|
|
123
193
|
"""The audit, for one reader. ``reader_user`` is who is asking: a user-scoped claim is
|
|
@@ -165,6 +235,10 @@ def report(ledger, recheck: bool = False, cwd: Optional[str] = None, timeout: Op
|
|
|
165
235
|
"signatures": {},
|
|
166
236
|
"unsigned_by_registered_signers": {},
|
|
167
237
|
"verifications_by_unregistered_identities": {},
|
|
238
|
+
"roster_problems": list(getattr(ledger, "roster_problems", None) or []),
|
|
239
|
+
"records_off_roster": {},
|
|
240
|
+
"records_after_retirement": {},
|
|
241
|
+
"verifications_outside_project_roster": {},
|
|
168
242
|
"recheck": [],
|
|
169
243
|
}
|
|
170
244
|
# measured history, computed from the ledger alone: counts, intervals, and the word
|
|
@@ -173,6 +247,12 @@ def report(ledger, recheck: bool = False, cwd: Optional[str] = None, timeout: Op
|
|
|
173
247
|
rep["reliability"] = measured["table"]
|
|
174
248
|
rep["same_lineage_agreement"] = measured["same_lineage_agreement"]
|
|
175
249
|
rep["staleness"] = measured["staleness"]
|
|
250
|
+
# roadmap 8.5.1: a project whose pinned sections are over config.compile_pinned_chars:
|
|
251
|
+
# the board shows every reader fewer contracts than are open, and the constraints, never
|
|
252
|
+
# cut, make the view longer than the cap. The audit names both, so a person consolidates
|
|
253
|
+
# the constraints, closes work, or raises the cap
|
|
254
|
+
from .compiler import pinned_over_cap # local: the compiler imports the trust layer too
|
|
255
|
+
rep["pinned_over_cap"] = pinned_over_cap(ledger, assessment, reader_user)
|
|
176
256
|
rep["judgment_calibration"] = measured["judgment_calibration"]
|
|
177
257
|
for c in claims:
|
|
178
258
|
a = c["author"]
|
|
@@ -337,6 +417,7 @@ def report(ledger, recheck: bool = False, cwd: Optional[str] = None, timeout: Op
|
|
|
337
417
|
# a ledger that never registered a key is not a ledger where everybody is
|
|
338
418
|
# unregistered: there is no registry to be outside of
|
|
339
419
|
rep["verifications_by_unregistered_identities"]["no_registry"] = True
|
|
420
|
+
_roster_sections(ledger, rep, by_id, reader_user)
|
|
340
421
|
if recheck:
|
|
341
422
|
for c in claims:
|
|
342
423
|
info = assessment[c["id"]]
|
|
@@ -367,9 +448,8 @@ def format_report(rep: dict) -> str:
|
|
|
367
448
|
lines.append(f"labels: unsigned by a registered signer={unsigned.get('total', 0)}, "
|
|
368
449
|
f"verifications by an unregistered identity={unregistered.get('total', 0)}")
|
|
369
450
|
if not rep.get("trusted", True):
|
|
370
|
-
lines.append("repo: UNTRUSTED -- config.json's policy keys
|
|
371
|
-
"
|
|
372
|
-
"cross_vendor_required, stale_after_days, reliability, max_clock_skew_seconds) "
|
|
451
|
+
lines.append("repo: UNTRUSTED -- config.json's policy keys "
|
|
452
|
+
f"({', '.join(sorted(UNTRUSTED_POLICY))}) "
|
|
373
453
|
"were ignored; run `agent-memory trust .` to honour them")
|
|
374
454
|
if not rep.get("recheck_allow_set"):
|
|
375
455
|
lines.append("note: config.recheck_allow is not set: the re-check lane is held to the starter "
|
|
@@ -383,7 +463,7 @@ def format_report(rep: dict) -> str:
|
|
|
383
463
|
lines.append("")
|
|
384
464
|
lines.append(f"## {title} ({len(rows)})")
|
|
385
465
|
for r in rows[:50]:
|
|
386
|
-
lines.append("- " + fmt(r))
|
|
466
|
+
lines.append("- " + _one_line(fmt(r)))
|
|
387
467
|
if len(rows) > 50:
|
|
388
468
|
lines.append(f"- ... {len(rows) - 50} more")
|
|
389
469
|
|
|
@@ -396,7 +476,7 @@ def format_report(rep: dict) -> str:
|
|
|
396
476
|
if note:
|
|
397
477
|
lines.append(f"- {note}")
|
|
398
478
|
for r in items:
|
|
399
|
-
lines.append("- " + fmt(r))
|
|
479
|
+
lines.append("- " + _one_line(fmt(r)))
|
|
400
480
|
if sec.get("total", 0) > len(items):
|
|
401
481
|
lines.append(f"- ... {sec['total'] - len(items)} more")
|
|
402
482
|
|
|
@@ -423,6 +503,12 @@ def format_report(rep: dict) -> str:
|
|
|
423
503
|
section("Taint chains", rep["taint_chains"], lambda r: f"{r['root']} -> " + ", ".join(t['id'] for t in r['tainted']))
|
|
424
504
|
section("Stale", rep["stale"], lambda r: f"{r['id']} {r['statement']} -- {'; '.join(r['reasons'])}")
|
|
425
505
|
section("Facts with no evidence (asserted)", rep["asserted_facts"], lambda r: f"{r['id']} [{r['by']}] {r['statement']}")
|
|
506
|
+
section("Pinned sections over config.compile_pinned_chars",
|
|
507
|
+
rep.get("pinned_over_cap", []),
|
|
508
|
+
lambda r: (f"{r['project']}{' (' + r['reader'] + ')' if r.get('reader') else ''}: {r['section']} "
|
|
509
|
+
f"takes {r['chars']} characters against a cap of {r['cap']}; "
|
|
510
|
+
+ ("kept whole in every view (constraints are never cut): consolidate them"
|
|
511
|
+
if r.get("kept_whole") else f"left out: {', '.join(r['left_out'])}")))
|
|
426
512
|
section("Contracts", rep["contracts"], lambda r: f"{r['id']} {r['status']}: {r['title']} ({r['claims']} claims {r['tiers']})")
|
|
427
513
|
section("Compilation receipts", rep["compilations"],
|
|
428
514
|
lambda r: f"{r['id']} {r['ts']} {r['role']}/{r['mode']} for {r['reader']}: {r['included']} in, {r['excluded']} out, {r['tokens']} tok")
|
|
@@ -454,6 +540,24 @@ def format_report(rep: dict) -> str:
|
|
|
454
540
|
f"{r['verdict']} at {r['ts']}",
|
|
455
541
|
note=("config.signers is empty: this ledger registered no key, so no identity in it "
|
|
456
542
|
"can be checked" if unregistered.get("no_registry") else None))
|
|
543
|
+
section("Roster entries set aside (fix them in config.json)", rep.get("roster_problems") or [], str)
|
|
544
|
+
no_roster = ("config.agents is empty: this ledger has no roster, so no identity in it is on or off one "
|
|
545
|
+
"(docs/PROTOCOL.md section 12)")
|
|
546
|
+
off = rep.get("records_off_roster") or {}
|
|
547
|
+
capped("Records by identities not on the roster", off,
|
|
548
|
+
lambda r: f"{r['id']} [{r['kind']}] {r['user']} via {r['harness']} at {r['ts']}",
|
|
549
|
+
note=no_roster if off.get("no_roster") else ROSTER_OUTSIDERS)
|
|
550
|
+
late = rep.get("records_after_retirement") or {}
|
|
551
|
+
capped("Records filed after their author retired", late,
|
|
552
|
+
lambda r: f"{r['id']} [{r['kind']}] {r['user']} (retired {r['retired']}) at {r['ts']}",
|
|
553
|
+
note=no_roster if late.get("no_roster") else None)
|
|
554
|
+
outside = rep.get("verifications_outside_project_roster") or {}
|
|
555
|
+
capped("Verifications by an identity outside the claim's project roster", outside,
|
|
556
|
+
lambda r: f"{r['id']} over {r['claim']} (project {r['project']}) [{r['user']} via {r['harness']}] "
|
|
557
|
+
f"{r['verdict']} at {r['ts']}",
|
|
558
|
+
note=(no_roster if outside.get("no_roster") else
|
|
559
|
+
"no roster entry names a project, so no verification is outside one"
|
|
560
|
+
if outside.get("no_projects") else ROSTER_OUTSIDERS))
|
|
457
561
|
lines.append("")
|
|
458
562
|
lines.append(f"## Verifier lineage (refutation rates) ({len(rep['lineage_matrix'])})")
|
|
459
563
|
for key, cell in sorted(rep["lineage_matrix"].items()):
|
|
@@ -476,4 +580,6 @@ def format_report(rep: dict) -> str:
|
|
|
476
580
|
lambda r: f"{r['claim']} {r['evidence']}: {r['outcome']} -- {r['detail']}"
|
|
477
581
|
+ (" [environment binding: staleness, not refutation]" if r.get("role") == "environment" else "")
|
|
478
582
|
+ (f" [{r['note']}]" if r.get("note") else ""))
|
|
479
|
-
|
|
583
|
+
# every line the report prints stays one line, whatever a record carried into it (the
|
|
584
|
+
# authors line, a harness, a user): one flatten here covers every section, old and new
|
|
585
|
+
return "\n".join(_one_line(line) for line in lines) + "\n"
|