strata-agent-memory 0.2.6__tar.gz → 0.2.8__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.6/strata_agent_memory.egg-info → strata_agent_memory-0.2.8}/PKG-INFO +1 -1
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/__init__.py +1 -1
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/cli.py +34 -10
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/compiler.py +107 -29
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/contracts.py +152 -7
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/evidence.py +122 -14
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/lane.py +255 -37
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/ledger.py +6 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/mcp_server.py +19 -9
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/recheck.py +88 -5
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/records.py +20 -2
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/remote.py +70 -6
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/render.py +7 -1
- strata_agent_memory-0.2.8/agent_memory/retrieval.py +205 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/serve.py +229 -1
- strata_agent_memory-0.2.8/deploy/lib/served-ledger.sh +58 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/README.md +7 -1
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/backup-tokens.sh +4 -2
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/install-replica-driver.sh +104 -71
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/onboard-agent.sh +9 -4
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/seed-board.sh +3 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/seed-topics.sh +3 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/upgrade-driver.sh +34 -7
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/pyproject.toml +17 -3
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8/strata_agent_memory.egg-info}/PKG-INFO +1 -1
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/strata_agent_memory.egg-info/SOURCES.txt +20 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_assessment_67b26eae.py +11 -0
- strata_agent_memory-0.2.8/tests/test_binding_refusal_97e36882.py +122 -0
- strata_agent_memory-0.2.8/tests/test_build_recheck_image_e4a93fd6.py +51 -0
- strata_agent_memory-0.2.8/tests/test_contract_amend_649e0b45.py +354 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_deploy_mac.py +19 -1
- strata_agent_memory-0.2.8/tests/test_install_lane_cb430a30.py +335 -0
- strata_agent_memory-0.2.8/tests/test_lane_derived_roots_817ab446.py +263 -0
- strata_agent_memory-0.2.8/tests/test_lane_nightly_84b22b63.py +203 -0
- strata_agent_memory-0.2.8/tests/test_lane_stop_63b7cf8b.py +159 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_ledger.py +1 -0
- strata_agent_memory-0.2.8/tests/test_memoryagentbench_60a8abfc.py +374 -0
- strata_agent_memory-0.2.8/tests/test_memsyco_7b16e8e4.py +553 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_packaging.py +5 -1
- strata_agent_memory-0.2.8/tests/test_proof_exclusions_e4a93fd6.py +80 -0
- strata_agent_memory-0.2.8/tests/test_recheck_away_207af145.py +276 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_recheck_sandbox_543e48a7.py +40 -6
- strata_agent_memory-0.2.8/tests/test_recheck_sandbox_nits_543e48a7.py +73 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_client.py +1 -1
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_release_gate_b3b3e487.py +2 -2
- strata_agent_memory-0.2.8/tests/test_reply_cap_cfbd7681.py +229 -0
- strata_agent_memory-0.2.8/tests/test_retrieval_plugin_9e166c2f.py +364 -0
- strata_agent_memory-0.2.8/tests/test_rollback_78710ebf.py +510 -0
- strata_agent_memory-0.2.8/tests/test_sandbox_image_e4a93fd6.py +320 -0
- strata_agent_memory-0.2.8/tests/test_served_ledger_6e8e49f3.py +265 -0
- strata_agent_memory-0.2.8/tests/test_tools_clone_4b72f0c1.py +232 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/LICENSE +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/README.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/__main__.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/approvals.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/audit.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/compact.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/export.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/hooks.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/identity.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/instances.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/kit.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/measure.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/memorizer.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/pathmap.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/relay/__init__.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/relay/adapters.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/relay/cli.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/relay/client.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/relay/core.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/reliability.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/roster.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/signing.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/stats.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/supersession.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/trust.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/agent_memory/wake.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/agent-keys.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/agent-session.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/install-hooks-mac.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/install-relay-driver.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/install-runner-driver.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/instances.example.json +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/relay-agent.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/mac/seed-mail-context.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/.claude-plugin/marketplace.json +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/README.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/bin/agent-memory-approvals +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/bin/agent-memory-wake +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/hooks/hooks.json +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/monitors/monitors.json +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/rules/approvals.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/skills/approvals/SKILL.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/skills/approve/SKILL.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/skills/inbox/SKILL.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/claude-code/skills/reject/SKILL.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/codex/skills/agent-memory-answer/SKILL.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/codex/skills/agent-memory-answer/agents/openai.yaml +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/codex/skills/agent-memory-approvals/SKILL.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/codex/skills/agent-memory-approvals/agents/openai.yaml +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/codex/skills/agent-memory-approvals/rules/approvals.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/plugins/codex/skills/agent-memory-approvals/scripts/agent-memory-approvals +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/relay/README.md +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/relay/agent-memory-relay@.service +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/relay/install-relay-mac.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/relay/install-relay.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/relay/macos/com.strata.agent-memory.relay.plist.template +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/deploy/relay/relay-launch.sh +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/setup.cfg +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/strata_agent_memory.egg-info/dependency_links.txt +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/strata_agent_memory.egg-info/entry_points.txt +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/strata_agent_memory.egg-info/requires.txt +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/strata_agent_memory.egg-info/top_level.txt +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_audit.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_audit_lane_numbers_ea89ba26.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_audit_measurement_13190a20.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_backlog_check.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_changelog_fragments.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_changelog_guard_shallow_57a2f78e.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_ci_docs_only_353ce010.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_cli.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_compact.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_compiler.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_compiler_render.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_config.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_contracts.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_deploy_relay.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_deploy_replica.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_deploy_tokens.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_evidence.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_find_root_b4b5019b.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_hooks.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_identity.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_issue_bridge.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_lane_63b7cf8b.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_lane_priority_779d51ba.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_ledger_index.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_live_wake_c217c336.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_local_lanes_sibling_1411a2b2.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_memorizer.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_outcome_commit_binding_b1de564d.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_outcome_needs_evidence_b1de564d.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_pathmap.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_phase2.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_plugins.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_receipt_reports_274e6199.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_recheck.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_recheck_remote_8185d2d3.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_records.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_redaction.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_adapters.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_codex.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_core.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_instance_2a2e3887.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_relay_release_526f5261.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_reliability.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_remote.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_remote_signing.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_review2_fixes.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_review3_fixes.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_review_fixes.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_scale.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_serve.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_signing.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_sit_fleet_1b24b0b1.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_sit_fleet_ci_0d9b76a2.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_sit_var_bindings_e802ca84.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_stats.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_supersedes_61416280.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_trust.py +0 -0
- {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.8}/tests/test_uv_lock_env_274e6199.py +0 -0
{strata_agent_memory-0.2.6/strata_agent_memory.egg-info → strata_agent_memory-0.2.8}/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.8
|
|
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
|
|
@@ -606,9 +606,10 @@ def _private_contract_refusal(args) -> Optional[int]:
|
|
|
606
606
|
|
|
607
607
|
|
|
608
608
|
def _acceptance_checks(args) -> List[dict]:
|
|
609
|
-
"""The acceptance checks a `contract create` asks for,
|
|
610
|
-
|
|
611
|
-
|
|
609
|
+
"""The acceptance checks a `contract create` asks for, or a `contract update` amends the
|
|
610
|
+
list to (ctr_649e0b459a758929), in the order the flags read them. The remote lane files
|
|
611
|
+
the same record as the local one, so both build the list here: a check that got lost on
|
|
612
|
+
the way is a contract nobody can prove."""
|
|
612
613
|
acceptance = [{"kind": "command", "cmd": c, "expect_exit": 0} for c in args.check or []]
|
|
613
614
|
acceptance += [{"kind": "human", "statement": s} for s in args.human_check or []]
|
|
614
615
|
acceptance += [{"kind": "file", "path": f} for f in args.file_check or []]
|
|
@@ -637,8 +638,13 @@ def cmd_contract(args) -> int:
|
|
|
637
638
|
_print(out)
|
|
638
639
|
return 0 if signed else 3
|
|
639
640
|
if args.action == "update":
|
|
640
|
-
|
|
641
|
-
|
|
641
|
+
try:
|
|
642
|
+
rec = contracts.update(ledger, author, scope, args.id, args.status, note=args.note, claims=args.claims or (),
|
|
643
|
+
assigned_to=getattr(args, "assign", None),
|
|
644
|
+
acceptance_checks=_acceptance_checks(args) or None)
|
|
645
|
+
except ValidationError as exc:
|
|
646
|
+
print(f"rejected: {exc}", file=sys.stderr)
|
|
647
|
+
return 3
|
|
642
648
|
out = {"id": rec["id"], "status": args.status}
|
|
643
649
|
signed = _sign_after(args, ledger, author, scope, rec, out)
|
|
644
650
|
_print(out)
|
|
@@ -1479,7 +1485,10 @@ def _remote_recheck(args, client) -> int:
|
|
|
1479
1485
|
max=args.max, cwd=root, timeout=args.timeout, path_map=path_map,
|
|
1480
1486
|
dry_run=args.dry_run, min_age_days=args.min_age_days, force=args.force,
|
|
1481
1487
|
assessment=view.assessment, file=file,
|
|
1482
|
-
policy=_sandbox_policy(args, _local_sandbox(view.config, root))
|
|
1488
|
+
policy=_sandbox_policy(args, _local_sandbox(view.config, root)),
|
|
1489
|
+
# a re-run away from the outcome's bound commit refutes nothing
|
|
1490
|
+
# (the lane's rule, ctr_207af145a59463ed)
|
|
1491
|
+
away_is_error=True)
|
|
1483
1492
|
except ValueError as exc:
|
|
1484
1493
|
print(f"recheck: {exc}", file=sys.stderr)
|
|
1485
1494
|
return 2
|
|
@@ -1679,7 +1688,8 @@ def _remote_contract_update(args, client) -> int:
|
|
|
1679
1688
|
return 2
|
|
1680
1689
|
claims = list(args.claims or [])
|
|
1681
1690
|
body = contracts.update_body(args.id, args.status, note=args.note, claims=claims,
|
|
1682
|
-
assigned_to=getattr(args, "assign", None)
|
|
1691
|
+
assigned_to=getattr(args, "assign", None),
|
|
1692
|
+
acceptance_checks=_acceptance_checks(args) or None)
|
|
1683
1693
|
payload = {"kind": "contract_update", "body": body, "derived_from": [args.id, *claims]}
|
|
1684
1694
|
rec, _ = client.post_record(_scoped_payload(payload, _remote_scope(args, client)))
|
|
1685
1695
|
out = {"id": rec["id"], "status": rec["body"]["status"]}
|
|
@@ -1704,9 +1714,16 @@ def _remote_contract_accept(args, client) -> int:
|
|
|
1704
1714
|
if local is None:
|
|
1705
1715
|
return 2
|
|
1706
1716
|
served = _ServedLedger(client, local)
|
|
1717
|
+
# read once, before anything runs: the checks the replica says the contract answers to
|
|
1718
|
+
# now, an amendment's included (the served view holds no contract_updates to find one
|
|
1719
|
+
# in, ctr_649e0b459a758929), and the status the run reports it started under, which
|
|
1720
|
+
# ``contracts.run_acceptance`` reads before its checks run too (PROTOCOL 4.1)
|
|
1721
|
+
started = client.contract_status(args.id)
|
|
1722
|
+
checks = started.get("acceptance")
|
|
1707
1723
|
results = contracts.run_acceptance(served, args.id, cwd=args.cwd or str(local.root),
|
|
1708
1724
|
timeout=args.timeout, file=False,
|
|
1709
|
-
policy=_sandbox_policy(args, served.config)
|
|
1725
|
+
policy=_sandbox_policy(args, served.config),
|
|
1726
|
+
checks=checks if isinstance(checks, list) else None)
|
|
1710
1727
|
# asked once and used for the receipts and the run alike: a --scope this client typed
|
|
1711
1728
|
# has to reach the server the way `claim` and `retract` send it, and a project scope
|
|
1712
1729
|
# is still the server's own to stamp
|
|
@@ -1716,7 +1733,7 @@ def _remote_contract_accept(args, client) -> int:
|
|
|
1716
1733
|
if body and body.get("kind") in ("command", "test"):
|
|
1717
1734
|
rec, _ = client.post_record(_scoped_payload({"kind": "evidence", "body": body}, scope))
|
|
1718
1735
|
r["receipt"] = rec["id"]
|
|
1719
|
-
status =
|
|
1736
|
+
status = started["status"] # an acceptance run reports, it does not promote
|
|
1720
1737
|
client.post_record(_scoped_payload({"kind": "contract_update", "body": {
|
|
1721
1738
|
"contract": args.id, "status": status,
|
|
1722
1739
|
"acceptance": [{"check": r["check"], "outcome": r["outcome"], "detail": r["detail"],
|
|
@@ -2349,6 +2366,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2349
2366
|
c.add_argument("--note")
|
|
2350
2367
|
c.add_argument("--claims", nargs="*")
|
|
2351
2368
|
c.add_argument("--assign", help="hand the contract to this agent (the board follows the latest assignee)")
|
|
2369
|
+
# an amendment of the acceptance checks: the whole list, as `contract create` takes it
|
|
2370
|
+
# (ctr_649e0b459a758929); refused under the contract's assignee's own key
|
|
2371
|
+
c.add_argument("--check", action="append", help="amend the acceptance: a command expecting exit 0 (repeatable)")
|
|
2372
|
+
c.add_argument("--human-check", action="append", help="amend the acceptance: a human sign-off (repeatable)")
|
|
2373
|
+
c.add_argument("--file-check", action="append", help="amend the acceptance: a file that must exist (repeatable)")
|
|
2374
|
+
c.add_argument("--acceptance-json", help="amend the acceptance: a JSON list of checks, replacing the contract's")
|
|
2352
2375
|
_add_identity(c)
|
|
2353
2376
|
_add_signing(c)
|
|
2354
2377
|
c = cs.add_parser("from-issue", help="create a contract from a GitHub issue (needs the gh CLI)")
|
|
@@ -2419,7 +2442,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2419
2442
|
"verifications under the running identity; with --remote, the "
|
|
2420
2443
|
"replica's claims, re-run here under the replica's re-check policy")
|
|
2421
2444
|
s.add_argument("--only", choices=list(recheck_mod.ONLY_CHOICES), default="unverified",
|
|
2422
|
-
help="unverified (tier evidenced
|
|
2445
|
+
help="unverified (tier evidenced or self-verified, whatever inconclusives it carries; the "
|
|
2446
|
+
"default), stale, or all (everything but refuted/tainted)")
|
|
2423
2447
|
s.add_argument("--type", action="append",
|
|
2424
2448
|
help="restrict to these claim types, comma-separated (e.g. outcome,fact)")
|
|
2425
2449
|
s.add_argument("--max", type=int, help="stop after this many claims")
|
|
@@ -14,6 +14,7 @@ Roles and modes follow the fork/isolate rule:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import re
|
|
17
|
+
import sys
|
|
17
18
|
from dataclasses import dataclass, field
|
|
18
19
|
from datetime import timedelta
|
|
19
20
|
from typing import Callable, Dict, Iterable, List, Optional, Tuple
|
|
@@ -142,6 +143,9 @@ class Compiled:
|
|
|
142
143
|
pinned_cap: Optional[int] = None
|
|
143
144
|
pinned_cut: Dict[str, int] = field(default_factory=dict)
|
|
144
145
|
pinned_full: Dict[str, int] = field(default_factory=dict)
|
|
146
|
+
# the retrieval plugin that took part (ctr_9e166c2f291b829b): {plugin, share, ids}, and
|
|
147
|
+
# error when it could not run; None when config.retrieval names none
|
|
148
|
+
retrieval: Optional[Dict] = None
|
|
145
149
|
|
|
146
150
|
def included_ids(self) -> List[str]:
|
|
147
151
|
return [i.id for items in self.sections.values() for i in items]
|
|
@@ -590,6 +594,10 @@ def compile_context(
|
|
|
590
594
|
else:
|
|
591
595
|
out.sections["working"].append(item)
|
|
592
596
|
|
|
597
|
+
# the retrieval plugin (ctr_9e166c2f291b829b): the ids it finds for the task, of which
|
|
598
|
+
# the reader sees only what this compile's own policy filter let through (candidates)
|
|
599
|
+
retrieval_cfg, retrieved = _retrieval(ledger, task, {item.id: item for item, _ in candidates}, out, assessment)
|
|
600
|
+
|
|
593
601
|
seen_dnr = set()
|
|
594
602
|
for it in do_not_retry:
|
|
595
603
|
key = (it.id, it.statement)
|
|
@@ -619,35 +627,72 @@ def compile_context(
|
|
|
619
627
|
# costed provisionally here and the difference is charged once it is known
|
|
620
628
|
provisional = _obligations(role, mode, round_no, bool(out.sections["cross_check"]), bool(contract_id), lens)
|
|
621
629
|
overhead = overhead_tokens(out, provisional)
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
for
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
used
|
|
649
|
-
|
|
650
|
-
|
|
630
|
+
def allocate(reserve: int, found: List[Item], fixed: Optional[List[Item]] = None):
|
|
631
|
+
"""(kept, used, left out for the budget): pass one fills each section up to its
|
|
632
|
+
share, the retrieval plugin's ``found`` fill its section up to ``reserve``, and pass
|
|
633
|
+
two fills leftovers in priority order. ``fixed`` is the constraints section as
|
|
634
|
+
compile alone keeps it: the plugin's share is never taken from what always applies
|
|
635
|
+
(the review of ctr_9e166c2f291b829b)."""
|
|
636
|
+
used = overhead
|
|
637
|
+
kept: Dict[str, List[Item]] = {name: [] for name in SECTION_ORDER}
|
|
638
|
+
remaining_by_section: Dict[str, List[Item]] = {name: [] for name in SECTION_ORDER}
|
|
639
|
+
# pass 1: each section up to its share; pass 2: fill leftovers in priority order
|
|
640
|
+
for name in SECTION_ORDER:
|
|
641
|
+
if name == "constraints" and fixed is not None:
|
|
642
|
+
kept[name] = list(fixed)
|
|
643
|
+
used += sum(cost_of(item) for item in fixed)
|
|
644
|
+
held = {id(item) for item in fixed}
|
|
645
|
+
remaining_by_section[name] = [item for item in out.sections[name] if id(item) not in held]
|
|
646
|
+
continue
|
|
647
|
+
cap = int(budget * SECTION_SHARE[name])
|
|
648
|
+
# half of the constraints section is kept for its human-filed items, which arrive
|
|
649
|
+
# in standing order, so the freshly filed ones compete for the other half only
|
|
650
|
+
reserved = int(cap * CONSTRAINT_HUMAN_RESERVE) if name == "constraints" else 0
|
|
651
|
+
section_used = unreserved_used = 0
|
|
652
|
+
for item in out.sections[name]:
|
|
653
|
+
cost = cost_of(item)
|
|
654
|
+
reserve_eligible = bool(reserved) and item.is_human
|
|
655
|
+
fits_share = reserve_eligible or unreserved_used + cost <= cap - reserved
|
|
656
|
+
if section_used + cost <= cap and fits_share and used + cost <= budget - reserve:
|
|
657
|
+
kept[name].append(item)
|
|
658
|
+
section_used += cost
|
|
659
|
+
unreserved_used += 0 if reserve_eligible else cost
|
|
660
|
+
used += cost
|
|
661
|
+
else:
|
|
662
|
+
remaining_by_section[name].append(item)
|
|
663
|
+
taken = {item.id for items in kept.values() for item in items}
|
|
664
|
+
if found:
|
|
665
|
+
# what the plugin found and pass 1 did not already keep, in the plugin's order,
|
|
666
|
+
# up to its share: a record shown once, in the section that placed it first
|
|
667
|
+
kept["retrieved"], spent = [], 0
|
|
668
|
+
for item in found:
|
|
669
|
+
cost = cost_of(item)
|
|
670
|
+
if item.id not in taken and spent + cost <= reserve and used + cost <= budget:
|
|
671
|
+
kept["retrieved"].append(item)
|
|
672
|
+
taken.add(item.id)
|
|
673
|
+
spent += cost
|
|
674
|
+
used += cost
|
|
675
|
+
left_out: List[str] = []
|
|
676
|
+
for name in SECTION_ORDER:
|
|
677
|
+
for item in remaining_by_section[name]:
|
|
678
|
+
if item.id in taken:
|
|
679
|
+
continue # the retrieved section placed it
|
|
680
|
+
cost = cost_of(item)
|
|
681
|
+
if used + cost <= budget:
|
|
682
|
+
kept[name].append(item)
|
|
683
|
+
used += cost
|
|
684
|
+
else:
|
|
685
|
+
left_out.append(item.id)
|
|
686
|
+
return kept, used, left_out
|
|
687
|
+
|
|
688
|
+
# compile alone; and when the retrieval plugin found something, again with its share
|
|
689
|
+
# held back from every section but the constraints, which keep what compile alone
|
|
690
|
+
# gives them. A plugin that finds nothing changes nothing (BENCHMARK-PLAN 1.2)
|
|
691
|
+
kept, used, left_out = allocate(0, [])
|
|
692
|
+
if retrieved:
|
|
693
|
+
kept, used, left_out = allocate(int(budget * retrieval_cfg["share"]), retrieved, fixed=kept["constraints"])
|
|
694
|
+
out.retrieval["ids"] = [item.id for item in kept["retrieved"]]
|
|
695
|
+
out.excluded += [(rid, "budget") for rid in left_out]
|
|
651
696
|
out.sections = kept
|
|
652
697
|
out.used_tokens = used
|
|
653
698
|
|
|
@@ -668,6 +713,34 @@ def compile_context(
|
|
|
668
713
|
return out
|
|
669
714
|
|
|
670
715
|
|
|
716
|
+
def _retrieval(ledger, task: Optional[str], eligible: Dict[str, "Item"], out: "Compiled",
|
|
717
|
+
assessment: Optional[dict] = None):
|
|
718
|
+
"""(the plugin's settings or None, the eligible items it found, best first), and
|
|
719
|
+
``out.retrieval`` set to what the receipt records. A plugin that cannot run leaves the
|
|
720
|
+
view as compile alone makes it, and says so: on the receipt, and in one stderr line."""
|
|
721
|
+
from . import retrieval as retrieval_mod # local: retrieval imports this module
|
|
722
|
+
try:
|
|
723
|
+
cfg = retrieval_mod.settings(ledger.config)
|
|
724
|
+
except retrieval_mod.RetrievalError as exc:
|
|
725
|
+
out.retrieval = {"plugin": retrieval_mod.configured_name(ledger.config), "share": None, "ids": [],
|
|
726
|
+
"error": str(exc)}
|
|
727
|
+
print(f"agent-memory: the retrieval plugin did not run ({exc}); compiled without it", file=sys.stderr)
|
|
728
|
+
return None, []
|
|
729
|
+
if cfg is None:
|
|
730
|
+
return None, []
|
|
731
|
+
out.retrieval = {"plugin": cfg["plugin"], "share": cfg["share"], "ids": []}
|
|
732
|
+
if not task:
|
|
733
|
+
return cfg, [] # nothing to search by: a view with no task is compile's alone
|
|
734
|
+
try:
|
|
735
|
+
found = retrieval_mod.retrieve(ledger, task, cfg, assessment)
|
|
736
|
+
except retrieval_mod.RetrievalError as exc:
|
|
737
|
+
out.retrieval["error"] = str(exc)
|
|
738
|
+
print(f"agent-memory: the retrieval plugin {cfg['plugin']} did not run ({exc}); compiled without it",
|
|
739
|
+
file=sys.stderr)
|
|
740
|
+
return cfg, []
|
|
741
|
+
return cfg, [eligible[rid] for rid in found if rid in eligible]
|
|
742
|
+
|
|
743
|
+
|
|
671
744
|
def compiled_from_json(payload: dict) -> Compiled:
|
|
672
745
|
"""Rebuild a Compiled from ``render.render_json``'s payload.
|
|
673
746
|
|
|
@@ -690,6 +763,7 @@ def compiled_from_json(payload: dict) -> Compiled:
|
|
|
690
763
|
# saying it was cut
|
|
691
764
|
board=[dict(row) for row in payload.get("board") or []],
|
|
692
765
|
)
|
|
766
|
+
out.retrieval = payload.get("retrieval") if isinstance(payload.get("retrieval"), dict) else None
|
|
693
767
|
display = payload.get("reader_display")
|
|
694
768
|
out.reader_display = display if isinstance(display, str) and display else None
|
|
695
769
|
out.config_notes = [n for n in payload.get("config_notes") or [] if isinstance(n, str)]
|
|
@@ -840,6 +914,10 @@ def record_compilation(ledger, compiled: Compiled, rendered: str, render_name: s
|
|
|
840
914
|
# which reliability numbers the reader was shown, or null when annotation was off
|
|
841
915
|
"reliability": compiled.reliability_meta,
|
|
842
916
|
}
|
|
917
|
+
if compiled.retrieval is not None:
|
|
918
|
+
# which configuration produced the view (ctr_9e166c2f291b829b): the plugin, its
|
|
919
|
+
# share, the ids it added, and why it did not run when it did not
|
|
920
|
+
body["retrieval"] = compiled.retrieval
|
|
843
921
|
# which repository the view was cut to, and where it was served and asked from: set
|
|
844
922
|
# only when they are, so a plain view's receipt is what it always was
|
|
845
923
|
for key, value in (("repo", compiled.repo), ("served", compiled.served or None),
|
|
@@ -181,6 +181,116 @@ def _linked_claims(ledger, contract_id: str, extra: Iterable[str] = ()) -> List[
|
|
|
181
181
|
return out
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
def _assignee_of(body: dict) -> Optional[str]:
|
|
185
|
+
value = (body or {}).get("assigned_to")
|
|
186
|
+
return value.strip().lower() if isinstance(value, str) and value.strip() else None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def assignee_now(ledger, contract_id: str) -> Optional[str]:
|
|
190
|
+
"""Who holds the contract: the latest update that names an assignee, else the one it
|
|
191
|
+
was filed with (what ``status_of`` reports as ``assigned_to``)."""
|
|
192
|
+
rec = ledger.get(contract_id)
|
|
193
|
+
held = _assignee_of((rec or {}).get("body") or {})
|
|
194
|
+
for u in sorted(ledger.contract_updates(contract_id), key=sort_key):
|
|
195
|
+
held = _assignee_of(u["body"]) or held
|
|
196
|
+
return held
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _may_amend(config: dict, author: dict) -> bool:
|
|
200
|
+
"""A person, or the ledger's PM (config.pm): an agent's standing to amend a bar would
|
|
201
|
+
rest on an assignment any token can file, so no other agent has it."""
|
|
202
|
+
return author.get("kind") == "human" or is_pm(config, author)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _names_of(author: dict) -> set:
|
|
206
|
+
"""The names that mean ``author`` when a contract is assigned: its user id and the local
|
|
207
|
+
part the relay addresses it by, as a reader is matched (``compiler._reader_names``,
|
|
208
|
+
PROTOCOL section 11). Every holder test here goes through it (the review of #148)."""
|
|
209
|
+
from .compiler import _reader_names # local: compiler imports this module
|
|
210
|
+
return _reader_names(author)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _has_note(body: dict) -> bool:
|
|
214
|
+
note = body.get("note")
|
|
215
|
+
return isinstance(note, str) and bool(note.strip())
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def amendment_refusal(ledger, author: dict, contract_id: str, assigned_to: Optional[str] = None,
|
|
219
|
+
note: Optional[str] = None) -> Optional[str]:
|
|
220
|
+
"""Why ``author`` may not amend the contract's acceptance checks, or None
|
|
221
|
+
(ctr_649e0b459a758929). The worker never writes their own bar, so only a person or
|
|
222
|
+
the PM amends one. Reassignment is open to any token, so "anybody but the assignee"
|
|
223
|
+
would let the assignee reassign the contract away and amend it in the next update,
|
|
224
|
+
and would leave an unassigned contract to whoever wants it (the review of this
|
|
225
|
+
story). The PM does not amend a contract it holds, or one the same update hands to
|
|
226
|
+
it; a person may, as a person may reopen a closed one: the bar is the person's to set
|
|
227
|
+
(the PE's ruling). An amendment is a decision on the record, so it carries a note.
|
|
228
|
+
A contract names its holder by user id or by local part, and both mean the author."""
|
|
229
|
+
names = _names_of(author)
|
|
230
|
+
if not _may_amend(getattr(ledger, "config", None) or {}, author):
|
|
231
|
+
return (f"{author.get('user') or 'this agent'} may not amend contract {contract_id}'s acceptance checks: "
|
|
232
|
+
f"an amendment is a person's or the PM's (config.pm), since the worker never writes their own bar")
|
|
233
|
+
if not (note or "").strip():
|
|
234
|
+
return (f"an amendment of contract {contract_id}'s acceptance checks is a decision on the record: "
|
|
235
|
+
f"say why in --note")
|
|
236
|
+
if author.get("kind") == "human":
|
|
237
|
+
return None
|
|
238
|
+
holder = assignee_now(ledger, contract_id)
|
|
239
|
+
if holder and holder in names:
|
|
240
|
+
return (f"contract {contract_id} is assigned to {holder}, and the assignee never amends its own "
|
|
241
|
+
f"acceptance checks: the PM or somebody else does")
|
|
242
|
+
handed = _assignee_of({"assigned_to": assigned_to})
|
|
243
|
+
if handed and handed in names:
|
|
244
|
+
return (f"an amendment of contract {contract_id}'s acceptance checks may not assign the contract to its "
|
|
245
|
+
f"own author ({handed})")
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def amendments(ledger, contract_id: str) -> List[dict]:
|
|
250
|
+
"""The contract_updates that amend the contract's acceptance checks, oldest first.
|
|
251
|
+
|
|
252
|
+
Refused where it is filed, and not counted where it is read, by the same rules: an
|
|
253
|
+
amendment by anybody but a person or the PM, or with no note, or whose agent author
|
|
254
|
+
held the contract when it was filed (by user id or local part), or which handed it to
|
|
255
|
+
that author, changes nothing (one written by hand, or through a replica that did not
|
|
256
|
+
know the key). The history is folded in ``sort_key`` order, so every machine counts
|
|
257
|
+
the same ones."""
|
|
258
|
+
config = getattr(ledger, "config", None) or {}
|
|
259
|
+
rec = ledger.get(contract_id)
|
|
260
|
+
held = _assignee_of((rec or {}).get("body") or {})
|
|
261
|
+
counted = []
|
|
262
|
+
for u in sorted(ledger.contract_updates(contract_id), key=sort_key):
|
|
263
|
+
body = u.get("body") or {}
|
|
264
|
+
if "acceptance_checks" in body and "acceptance" not in body:
|
|
265
|
+
author = u.get("author") or {}
|
|
266
|
+
names = _names_of(author)
|
|
267
|
+
handed = _assignee_of(body)
|
|
268
|
+
held_by_author = author.get("kind") != "human" and bool(
|
|
269
|
+
(held and held in names) or (handed and handed in names))
|
|
270
|
+
if _may_amend(config, author) and _has_note(body) and not held_by_author:
|
|
271
|
+
counted.append(u)
|
|
272
|
+
held = _assignee_of(body) or held
|
|
273
|
+
return counted
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def acceptance_of(ledger, contract_id: str) -> tuple:
|
|
277
|
+
"""(the acceptance checks the contract answers to, the amendment that set them or
|
|
278
|
+
None): the latest counted amendment's list, else the one the contract was filed with.
|
|
279
|
+
Every reader and runner of the checks goes through here (ctr_649e0b459a758929)."""
|
|
280
|
+
rec = ledger.get(contract_id)
|
|
281
|
+
if rec is None or rec.get("kind") != "contract":
|
|
282
|
+
raise KeyError(f"no contract {contract_id}")
|
|
283
|
+
if not hasattr(ledger, "contract_updates"):
|
|
284
|
+
# a view that holds no contract_updates cannot see an amendment, and running the list
|
|
285
|
+
# the contract was filed with would be the very slip this reads past: a caller with
|
|
286
|
+
# such a view passes the checks the replica reports (``run_acceptance(checks=)``)
|
|
287
|
+
raise TypeError(f"the acceptance of {contract_id} needs a ledger that holds its contract_updates")
|
|
288
|
+
latest = amendments(ledger, contract_id)
|
|
289
|
+
if latest:
|
|
290
|
+
return latest[-1]["body"]["acceptance_checks"], latest[-1]["id"]
|
|
291
|
+
return rec["body"]["acceptance"], None
|
|
292
|
+
|
|
293
|
+
|
|
184
294
|
def acceptance_runs(ledger, contract_id: str) -> List[dict]:
|
|
185
295
|
"""Acceptance-run contract_updates for a contract, oldest first."""
|
|
186
296
|
runs = [u for u in ledger.contract_updates(contract_id) if u["body"].get("acceptance")]
|
|
@@ -270,6 +380,13 @@ def verification_basis(ledger, contract_id: str, assessment=None) -> dict:
|
|
|
270
380
|
return out
|
|
271
381
|
latest = runs[-1]
|
|
272
382
|
out["run"] = latest["id"]
|
|
383
|
+
amended = amendments(ledger, contract_id)
|
|
384
|
+
if amended and sort_key(amended[-1]) > sort_key(latest):
|
|
385
|
+
# the run asked the checks the contract answered to then, not the ones it answers to
|
|
386
|
+
# now (ctr_649e0b459a758929)
|
|
387
|
+
out["reason"] = (f"the acceptance was amended by {amended[-1]['id']} after the latest run; "
|
|
388
|
+
f"run `agent-memory contract accept {contract_id}`")
|
|
389
|
+
return out
|
|
273
390
|
if not _run_ok(latest):
|
|
274
391
|
failing = [r.get("check", {}).get("cmd") or r.get("check", {}).get("path") or r.get("check", {}).get("kind")
|
|
275
392
|
for r in latest["body"]["acceptance"] if r.get("outcome") not in ("match", "skipped")]
|
|
@@ -452,6 +569,11 @@ def transition_refusal(ledger, rec: dict, assessment=None) -> Optional[str]:
|
|
|
452
569
|
note=note if isinstance(note, str) else None, assessment=assessment)
|
|
453
570
|
except ValidationError as exc:
|
|
454
571
|
return str(exc)
|
|
572
|
+
if "acceptance_checks" in body:
|
|
573
|
+
assigned = body.get("assigned_to")
|
|
574
|
+
return amendment_refusal(ledger, rec.get("author") or {}, contract_id,
|
|
575
|
+
assigned if isinstance(assigned, str) else None,
|
|
576
|
+
note=note if isinstance(note, str) else None)
|
|
455
577
|
return None
|
|
456
578
|
|
|
457
579
|
|
|
@@ -461,6 +583,7 @@ def update_body(
|
|
|
461
583
|
note: Optional[str] = None,
|
|
462
584
|
claims: Iterable[str] = (),
|
|
463
585
|
assigned_to: Optional[str] = None,
|
|
586
|
+
acceptance_checks: Optional[List[dict]] = None,
|
|
464
587
|
) -> dict:
|
|
465
588
|
"""The body of a contract_update that asks for a move, without filing it.
|
|
466
589
|
|
|
@@ -473,6 +596,8 @@ def update_body(
|
|
|
473
596
|
body["note"] = note
|
|
474
597
|
if assigned_to:
|
|
475
598
|
body["assigned_to"] = assigned_to
|
|
599
|
+
if acceptance_checks:
|
|
600
|
+
body["acceptance_checks"] = list(acceptance_checks)
|
|
476
601
|
return body
|
|
477
602
|
|
|
478
603
|
|
|
@@ -486,13 +611,21 @@ def update(
|
|
|
486
611
|
claims: Iterable[str] = (),
|
|
487
612
|
tags: Iterable[str] = (),
|
|
488
613
|
assigned_to: Optional[str] = None,
|
|
614
|
+
acceptance_checks: Optional[List[dict]] = None,
|
|
489
615
|
) -> dict:
|
|
490
616
|
"""File a status transition, enforcing the rules above. ``assigned_to`` hands the
|
|
491
617
|
contract to another agent: the latest update that names one is who holds it, which
|
|
492
|
-
is what the compiled board shows (a PM reassigns with `contract update --assign`).
|
|
618
|
+
is what the compiled board shows (a PM reassigns with `contract update --assign`).
|
|
619
|
+
``acceptance_checks`` replaces the checks the contract answers to from here on
|
|
620
|
+
(``acceptance_of``); the assignee may not file one (``amendment_refusal``)."""
|
|
493
621
|
_check_transition(ledger, author, contract_id, status, claims, note=note)
|
|
622
|
+
if acceptance_checks:
|
|
623
|
+
refusal = amendment_refusal(ledger, author, contract_id, assigned_to, note=note)
|
|
624
|
+
if refusal:
|
|
625
|
+
raise ValidationError(refusal)
|
|
494
626
|
rec = new_record("contract_update", author, scope,
|
|
495
|
-
update_body(contract_id, status, note=note, claims=claims, assigned_to=assigned_to
|
|
627
|
+
update_body(contract_id, status, note=note, claims=claims, assigned_to=assigned_to,
|
|
628
|
+
acceptance_checks=acceptance_checks),
|
|
496
629
|
derived_from=[contract_id, *claims], tags=tags)
|
|
497
630
|
return ledger.append(rec)
|
|
498
631
|
|
|
@@ -522,6 +655,8 @@ def status_of(ledger, contract_id: str, assessment: Optional[dict] = None) -> di
|
|
|
522
655
|
for u in updates:
|
|
523
656
|
linked_ids.update(u["body"].get("claims", []))
|
|
524
657
|
claims = [ledger.get(i) for i in sorted(linked_ids) if ledger.get(i)]
|
|
658
|
+
checks, amended_by = acceptance_of(ledger, contract_id)
|
|
659
|
+
amended_note = next((u["body"].get("note") for u in updates if u["id"] == amended_by), None)
|
|
525
660
|
out = {
|
|
526
661
|
"id": contract_id,
|
|
527
662
|
"title": rec["body"]["title"],
|
|
@@ -537,7 +672,11 @@ def status_of(ledger, contract_id: str, assessment: Optional[dict] = None) -> di
|
|
|
537
672
|
# thread besides the assignee's (ctr_b3b3e487832b5c94): a relay asks the replica
|
|
538
673
|
"pm": pm_users(getattr(ledger, "config", None) or {}),
|
|
539
674
|
"issue": rec["body"].get("issue"),
|
|
540
|
-
"acceptance":
|
|
675
|
+
"acceptance": checks,
|
|
676
|
+
# the amendment the checks come from, or None for the list the contract was filed
|
|
677
|
+
# with, and the note that says why (an amendment is a decision on the record)
|
|
678
|
+
"acceptance_amended_by": amended_by,
|
|
679
|
+
"acceptance_amended_note": amended_note,
|
|
541
680
|
"updates": [{"ts": u["ts"], "status": u["body"]["status"], "by": u["author"].get("harness"), "note": u["body"].get("note")} for u in updates],
|
|
542
681
|
"acceptance_runs": [],
|
|
543
682
|
"last_acceptance_ok": False,
|
|
@@ -560,7 +699,7 @@ def status_of(ledger, contract_id: str, assessment: Optional[dict] = None) -> di
|
|
|
560
699
|
|
|
561
700
|
def run_acceptance(ledger, contract_id: str, cwd: Optional[str] = None, timeout: Optional[float] = 600,
|
|
562
701
|
author: Optional[dict] = None, scope: Optional[dict] = None, file: bool = True,
|
|
563
|
-
policy: Optional[dict] = None) -> List[dict]:
|
|
702
|
+
policy: Optional[dict] = None, checks: Optional[List[dict]] = None) -> List[dict]:
|
|
564
703
|
"""Run each acceptance check and return [{check, outcome, detail, receipt_body, receipt}].
|
|
565
704
|
|
|
566
705
|
With ``file=True`` and an author and scope, the command/test receipts and a
|
|
@@ -588,7 +727,12 @@ def run_acceptance(ledger, contract_id: str, cwd: Optional[str] = None, timeout:
|
|
|
588
727
|
except ev.SandboxError as exc: # asked for and malformed: no check runs at all
|
|
589
728
|
sandbox, unsandboxable = None, str(exc)
|
|
590
729
|
results = []
|
|
591
|
-
|
|
730
|
+
# what this runner saw when it started (PROTOCOL 4.1: the status its runner read before
|
|
731
|
+
# the checks ran), as the remote lane reads it, never a status a close filed mid-run
|
|
732
|
+
started = status_now(ledger, contract_id) if file and author and scope else None
|
|
733
|
+
# the checks it answers to now (``acceptance_of``); a caller whose ledger holds no
|
|
734
|
+
# updates (the remote lane's served view) passes the list the replica reports
|
|
735
|
+
for check in (checks if checks is not None else acceptance_of(ledger, contract_id)[0]):
|
|
592
736
|
kind = check.get("kind")
|
|
593
737
|
if kind == "command":
|
|
594
738
|
where = ev.resolved_directory(base / check["cwd"] if check.get("cwd") else base,
|
|
@@ -621,7 +765,8 @@ def run_acceptance(ledger, contract_id: str, cwd: Optional[str] = None, timeout:
|
|
|
621
765
|
detail = (f"exit {body['exit']} (expected {expect_exit})" + (f"; {body.get('summary')}" if body.get("summary") else "")
|
|
622
766
|
+ ("; timed out, and everything it started was stopped" if body.get("timed_out") else ""))
|
|
623
767
|
if sandbox is not None:
|
|
624
|
-
reason = ev.sandbox_failure(sandbox["image"], body, expect_exit, check["cmd"]
|
|
768
|
+
reason = (ev.sandbox_failure(sandbox["image"], body, expect_exit, check["cmd"],
|
|
769
|
+
unproven=ev.image_proof(sandbox, where)) if not ok else None)
|
|
625
770
|
if reason:
|
|
626
771
|
outcome, detail = "error", f"{reason}; {detail}"
|
|
627
772
|
detail = f"sandboxed: podman {sandbox['image']}; {detail}"
|
|
@@ -646,7 +791,7 @@ def run_acceptance(ledger, contract_id: str, cwd: Optional[str] = None, timeout:
|
|
|
646
791
|
body = r.get("receipt_body")
|
|
647
792
|
if body and body.get("kind") in ("command", "test"):
|
|
648
793
|
r["receipt"] = ledger.append(new_record("evidence", author, scope, body))["id"]
|
|
649
|
-
status =
|
|
794
|
+
status = started # what this runner saw when it started: a run reports, it never promotes
|
|
650
795
|
body = {
|
|
651
796
|
"contract": contract_id,
|
|
652
797
|
"status": status,
|