strata-agent-memory 0.2.6__tar.gz → 0.2.7__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.
Files changed (162) hide show
  1. {strata_agent_memory-0.2.6/strata_agent_memory.egg-info → strata_agent_memory-0.2.7}/PKG-INFO +1 -1
  2. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/__init__.py +1 -1
  3. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/cli.py +34 -10
  4. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/contracts.py +150 -6
  5. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/evidence.py +10 -2
  6. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/lane.py +255 -37
  7. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/ledger.py +5 -0
  8. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/mcp_server.py +3 -2
  9. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/recheck.py +88 -5
  10. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/records.py +20 -2
  11. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/remote.py +27 -1
  12. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/serve.py +212 -1
  13. strata_agent_memory-0.2.7/deploy/lib/served-ledger.sh +58 -0
  14. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/README.md +6 -0
  15. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/seed-board.sh +3 -0
  16. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/seed-topics.sh +3 -0
  17. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/upgrade-driver.sh +11 -0
  18. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/pyproject.toml +5 -2
  19. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7/strata_agent_memory.egg-info}/PKG-INFO +1 -1
  20. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/strata_agent_memory.egg-info/SOURCES.txt +10 -0
  21. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_assessment_67b26eae.py +11 -0
  22. strata_agent_memory-0.2.7/tests/test_contract_amend_649e0b45.py +354 -0
  23. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_deploy_mac.py +3 -1
  24. strata_agent_memory-0.2.7/tests/test_install_lane_cb430a30.py +335 -0
  25. strata_agent_memory-0.2.7/tests/test_lane_derived_roots_817ab446.py +263 -0
  26. strata_agent_memory-0.2.7/tests/test_lane_nightly_84b22b63.py +203 -0
  27. strata_agent_memory-0.2.7/tests/test_lane_stop_63b7cf8b.py +159 -0
  28. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_ledger.py +1 -0
  29. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_packaging.py +3 -1
  30. strata_agent_memory-0.2.7/tests/test_recheck_away_207af145.py +276 -0
  31. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_recheck_sandbox_543e48a7.py +16 -4
  32. strata_agent_memory-0.2.7/tests/test_recheck_sandbox_nits_543e48a7.py +73 -0
  33. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_release_gate_b3b3e487.py +2 -2
  34. strata_agent_memory-0.2.7/tests/test_rollback_78710ebf.py +510 -0
  35. strata_agent_memory-0.2.7/tests/test_served_ledger_6e8e49f3.py +261 -0
  36. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/LICENSE +0 -0
  37. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/README.md +0 -0
  38. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/__main__.py +0 -0
  39. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/approvals.py +0 -0
  40. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/audit.py +0 -0
  41. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/compact.py +0 -0
  42. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/compiler.py +0 -0
  43. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/export.py +0 -0
  44. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/hooks.py +0 -0
  45. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/identity.py +0 -0
  46. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/instances.py +0 -0
  47. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/kit.py +0 -0
  48. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/measure.py +0 -0
  49. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/memorizer.py +0 -0
  50. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/pathmap.py +0 -0
  51. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/relay/__init__.py +0 -0
  52. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/relay/adapters.py +0 -0
  53. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/relay/cli.py +0 -0
  54. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/relay/client.py +0 -0
  55. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/relay/core.py +0 -0
  56. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/reliability.py +0 -0
  57. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/render.py +0 -0
  58. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/roster.py +0 -0
  59. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/signing.py +0 -0
  60. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/stats.py +0 -0
  61. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/supersession.py +0 -0
  62. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/trust.py +0 -0
  63. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/agent_memory/wake.py +0 -0
  64. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/agent-keys.sh +0 -0
  65. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/agent-session.sh +0 -0
  66. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/backup-tokens.sh +0 -0
  67. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/install-hooks-mac.sh +0 -0
  68. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/install-relay-driver.sh +0 -0
  69. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/install-replica-driver.sh +0 -0
  70. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/install-runner-driver.sh +0 -0
  71. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/instances.example.json +0 -0
  72. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/onboard-agent.sh +0 -0
  73. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/relay-agent.sh +0 -0
  74. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/mac/seed-mail-context.sh +0 -0
  75. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/.claude-plugin/marketplace.json +0 -0
  76. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/README.md +0 -0
  77. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  78. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/bin/agent-memory-approvals +0 -0
  79. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/bin/agent-memory-wake +0 -0
  80. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/hooks/hooks.json +0 -0
  81. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/monitors/monitors.json +0 -0
  82. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/rules/approvals.md +0 -0
  83. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/skills/approvals/SKILL.md +0 -0
  84. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/skills/approve/SKILL.md +0 -0
  85. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/skills/inbox/SKILL.md +0 -0
  86. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/claude-code/skills/reject/SKILL.md +0 -0
  87. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/codex/skills/agent-memory-answer/SKILL.md +0 -0
  88. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/codex/skills/agent-memory-answer/agents/openai.yaml +0 -0
  89. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/codex/skills/agent-memory-approvals/SKILL.md +0 -0
  90. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/codex/skills/agent-memory-approvals/agents/openai.yaml +0 -0
  91. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/codex/skills/agent-memory-approvals/rules/approvals.md +0 -0
  92. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/plugins/codex/skills/agent-memory-approvals/scripts/agent-memory-approvals +0 -0
  93. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/relay/README.md +0 -0
  94. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/relay/agent-memory-relay@.service +0 -0
  95. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/relay/install-relay-mac.sh +0 -0
  96. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/relay/install-relay.sh +0 -0
  97. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/relay/macos/com.strata.agent-memory.relay.plist.template +0 -0
  98. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/deploy/relay/relay-launch.sh +0 -0
  99. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/setup.cfg +0 -0
  100. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/strata_agent_memory.egg-info/dependency_links.txt +0 -0
  101. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/strata_agent_memory.egg-info/entry_points.txt +0 -0
  102. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/strata_agent_memory.egg-info/requires.txt +0 -0
  103. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/strata_agent_memory.egg-info/top_level.txt +0 -0
  104. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_audit.py +0 -0
  105. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_audit_lane_numbers_ea89ba26.py +0 -0
  106. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_audit_measurement_13190a20.py +0 -0
  107. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_backlog_check.py +0 -0
  108. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_changelog_fragments.py +0 -0
  109. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_changelog_guard_shallow_57a2f78e.py +0 -0
  110. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_ci_docs_only_353ce010.py +0 -0
  111. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_cli.py +0 -0
  112. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_compact.py +0 -0
  113. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_compiler.py +0 -0
  114. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_compiler_render.py +0 -0
  115. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_config.py +0 -0
  116. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_contracts.py +0 -0
  117. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_deploy_relay.py +0 -0
  118. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_deploy_replica.py +0 -0
  119. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_deploy_tokens.py +0 -0
  120. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_evidence.py +0 -0
  121. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_find_root_b4b5019b.py +0 -0
  122. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_hooks.py +0 -0
  123. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_identity.py +0 -0
  124. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_issue_bridge.py +0 -0
  125. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_lane_63b7cf8b.py +0 -0
  126. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_lane_priority_779d51ba.py +0 -0
  127. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_ledger_index.py +0 -0
  128. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_live_wake_c217c336.py +0 -0
  129. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_local_lanes_sibling_1411a2b2.py +0 -0
  130. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_memorizer.py +0 -0
  131. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_outcome_commit_binding_b1de564d.py +0 -0
  132. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_outcome_needs_evidence_b1de564d.py +0 -0
  133. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_pathmap.py +0 -0
  134. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_phase2.py +0 -0
  135. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_plugins.py +0 -0
  136. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_receipt_reports_274e6199.py +0 -0
  137. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_recheck.py +0 -0
  138. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_recheck_remote_8185d2d3.py +0 -0
  139. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_records.py +0 -0
  140. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_redaction.py +0 -0
  141. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_adapters.py +0 -0
  142. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_client.py +0 -0
  143. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_codex.py +0 -0
  144. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_core.py +0 -0
  145. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_instance_2a2e3887.py +0 -0
  146. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_relay_release_526f5261.py +0 -0
  147. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_reliability.py +0 -0
  148. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_remote.py +0 -0
  149. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_remote_signing.py +0 -0
  150. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_review2_fixes.py +0 -0
  151. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_review3_fixes.py +0 -0
  152. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_review_fixes.py +0 -0
  153. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_scale.py +0 -0
  154. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_serve.py +0 -0
  155. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_signing.py +0 -0
  156. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_sit_fleet_1b24b0b1.py +0 -0
  157. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_sit_fleet_ci_0d9b76a2.py +0 -0
  158. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_sit_var_bindings_e802ca84.py +0 -0
  159. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_stats.py +0 -0
  160. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_supersedes_61416280.py +0 -0
  161. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_trust.py +0 -0
  162. {strata_agent_memory-0.2.6 → strata_agent_memory-0.2.7}/tests/test_uv_lock_env_274e6199.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strata-agent-memory
3
- Version: 0.2.6
3
+ Version: 0.2.7
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
@@ -9,4 +9,4 @@ see, what did it claim, and can the proof be re-run.
9
9
  The core is dependency-free (Python 3.10+ standard library only).
10
10
  """
11
11
 
12
- __version__ = "0.2.6"
12
+ __version__ = "0.2.7"
@@ -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, in the order the flags read
610
- them. The remote lane files the same contract as the local one, so both build the
611
- list here: a check that got lost on the way is a contract nobody can prove."""
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
- rec = contracts.update(ledger, author, scope, args.id, args.status, note=args.note, claims=args.claims or (),
641
- assigned_to=getattr(args, "assign", None))
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 = client.contract_status(args.id)["status"] # an acceptance run reports, it does not promote
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, default), stale, or all (everything but refuted/tainted)")
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")
@@ -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": rec["body"]["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
- for check in rec["body"]["acceptance"]:
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,
@@ -646,7 +790,7 @@ def run_acceptance(ledger, contract_id: str, cwd: Optional[str] = None, timeout:
646
790
  body = r.get("receipt_body")
647
791
  if body and body.get("kind") in ("command", "test"):
648
792
  r["receipt"] = ledger.append(new_record("evidence", author, scope, body))["id"]
649
- status = status_now(ledger, contract_id) # what this runner saw: a run reports, it never promotes
793
+ status = started # what this runner saw when it started: a run reports, it never promotes
650
794
  body = {
651
795
  "contract": contract_id,
652
796
  "status": status,
@@ -2150,6 +2150,11 @@ def _check_binding(body: dict, cwd: Optional[str] = None, strict: bool = False,
2150
2150
  f"without it): bind it, even at 0, with `agent-memory run --var "
2151
2151
  f"{gate} -- ...`")
2152
2152
  cmd, run_env, variables = body["cmd"], None, bound
2153
+ if unsets and not body.get("shell"):
2154
+ # what the receipt's `env -u` takes away is not in the environment the command
2155
+ # runs in, so not on a sandbox's podman line either (LANG, TZ); a shell line's
2156
+ # prefix is the shell's guess, and it unsets for its first command only
2157
+ run_env = {k: v for k, v in os.environ.items() if k not in unsets}
2153
2158
  if bound is not None or assigned:
2154
2159
  # exactly the receipt's SIT_* switches, however it bound them, and none of
2155
2160
  # the verifier's own; never its place variables (SIT_PLACE_VARS)
@@ -2365,8 +2370,11 @@ def recheck_claim(ledger, claim: dict, cwd: Optional[str] = None, strict: bool =
2365
2370
  role = binding_role(body)
2366
2371
  if role != "proof":
2367
2372
  entry["role"] = role
2368
- if body.get("path"):
2369
- entry["path"] = body["path"]
2373
+ # a file's path rides on its result whatever its role: a reader deciding whether that
2374
+ # file was re-checked in a given tree needs the path, not the claim's other bindings
2375
+ # (recheck.away_from_bound; the review of #146)
2376
+ if body.get("path") and (role != "proof" or body.get("kind") == "file"):
2377
+ entry["path"] = body["path"]
2370
2378
  if rid:
2371
2379
  answered.add(rid)
2372
2380
  results.append(entry)