agentflow-proof 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,238 @@
1
+ """Generator for the machine-readable CLI contract manifest.
2
+
3
+ ``docs/cli-contract.json`` is produced by ``scripts/gen_cli_contract.py`` from
4
+ :func:`build_cli_contract`; ``tests/test_stability_policy.py`` re-derives the
5
+ same inventory so any undocumented CLI surface drift fails CI.
6
+
7
+ The serialized argument records are a *stable projection* of the parser: they
8
+ deliberately expose only user-visible facts (names, requiredness, arity,
9
+ defaults, choices) and never argparse internals such as action class names or
10
+ ``type`` callable names, which docs/stability.md classifies as changeable
11
+ Python internals.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+
18
+ from .cli import build_parser
19
+
20
+
21
+ STEP_EVENT = {
22
+ "schema_version": "string", "event": "string", "step_id": "string",
23
+ "attempt_id": "string", "agent_id": "string|null", "recorded_at": "string",
24
+ "lease_expires_at": "string|null", "reason": "string|null",
25
+ "reason_code": "string|null", "findings": "array|null",
26
+ "amends_attempt": "string|null", "amends_completed_at": "string|null",
27
+ }
28
+ COMMAND_RECEIPT = {
29
+ "schema_version": "string", "id": "string", "step_id": "string",
30
+ "attempt_id": "string", "command": "array", "cwd": "string", "env_names": "array",
31
+ "gate": "string|null", "decision": "string", "risk": "object", "provenance": "string",
32
+ "started_at": "string", "finished_at": "string", "exit_code": "integer",
33
+ "timed_out": "boolean", "timeout_seconds": "number|null", "truncated": "boolean",
34
+ "stdout_path": "string", "stdout_sha256": "string", "stdout_truncated": "boolean",
35
+ "stderr_path": "string", "stderr_sha256": "string", "stderr_truncated": "boolean",
36
+ }
37
+ FILE_RECEIPT = {
38
+ "schema_version": "string", "id": "string", "step_id": "string",
39
+ "attempt_id": "string", "path": "string", "previous_path": "string|null",
40
+ "change_kind": "string", "before_git_blob": "string|null", "after_sha256": "string|null",
41
+ "diff_engine": "string", "diff_algorithm": "string", "diff_command_version": "string",
42
+ "diff_unified": "integer", "hunks": "array", "hunk_attribution": "string",
43
+ "recorded_at": "string",
44
+ }
45
+ VERIFICATION = {
46
+ "schema_version": "string", "id": "string", "scope": "string", "status": "string",
47
+ "step_id": "string|null", "attempt_id": "string|null", "strict": "boolean",
48
+ "replay": "boolean", "findings": "array", "recorded_at": "string",
49
+ }
50
+ RESUMABILITY = {
51
+ "type": "object|null",
52
+ "keys": {
53
+ "contract": {
54
+ "type": "object",
55
+ "keys": {
56
+ "plan_schema_version": "string|null",
57
+ "plan_sha256": "string|null",
58
+ "locked": "boolean",
59
+ "locked_at": "string|null",
60
+ "execution_schema_version": "string|null",
61
+ "execution_contract_sha256": "string|null",
62
+ },
63
+ },
64
+ "agent_id": "string|null",
65
+ "step": {
66
+ "type": "object|null",
67
+ "keys": {
68
+ "id": "string",
69
+ "state": "string",
70
+ "completed": "boolean",
71
+ },
72
+ },
73
+ "attempt": {
74
+ "type": "object|null",
75
+ "keys": {
76
+ "id": "string",
77
+ "state": "string|null",
78
+ "owner": "string|null",
79
+ "open": "boolean",
80
+ },
81
+ },
82
+ "lease": {
83
+ "type": "object",
84
+ "keys": {
85
+ "policy": "string|null",
86
+ "ttl_minutes": "integer|null",
87
+ "grace_seconds": "integer|null",
88
+ "expires_at": "string|null",
89
+ "state": "string",
90
+ "exclusive": "boolean",
91
+ },
92
+ },
93
+ "receipts": {
94
+ "type": "object",
95
+ "keys": {
96
+ "commands": {
97
+ "type": "array",
98
+ "items": {
99
+ "type": "object",
100
+ "keys": {
101
+ "id": "string",
102
+ "gate": "string|null",
103
+ "command": "array|null",
104
+ "exit_code": "integer|null",
105
+ "decision": "string|null",
106
+ "timed_out": "boolean",
107
+ "provenance": "string|null",
108
+ "finished_at": "string|null",
109
+ },
110
+ },
111
+ },
112
+ "files": {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "object",
116
+ "keys": {
117
+ "id": "string",
118
+ "path": "string|null",
119
+ "change_kind": "string|null",
120
+ "recorded_at": "string|null",
121
+ },
122
+ },
123
+ },
124
+ },
125
+ },
126
+ "gates": {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "object",
130
+ "keys": {
131
+ "kind": "string",
132
+ "label": "string",
133
+ "status": "string",
134
+ "receipt_id": "string|null",
135
+ "evidence_id": "string|null",
136
+ },
137
+ },
138
+ },
139
+ "recovery_actions": {
140
+ "type": "array",
141
+ "items": {
142
+ "type": "object",
143
+ "keys": {
144
+ "action": "string",
145
+ "allowed": "boolean",
146
+ "automatic": "boolean",
147
+ "break_glass": "boolean",
148
+ "reason": "string",
149
+ },
150
+ },
151
+ },
152
+ "diagnostics": {
153
+ "type": "array",
154
+ "items": {
155
+ "type": "object",
156
+ "keys": {
157
+ "code": "string",
158
+ "message": "string",
159
+ "artifact": "string",
160
+ },
161
+ },
162
+ },
163
+ },
164
+ }
165
+
166
+ JSON_OUTPUTS = {
167
+ "pack inspect": [{"type": "object", "keys": {"status": "string", "errors": "array", "id": "string|null", "version": "string|null", "profiles": "array|null", "manifest_sha256": "string|null"}}],
168
+ "doctor": [{"type": "object", "keys": {"status": "string", "contract": "object|null", "findings": "array"}}],
169
+ "lock-plan": [{"type": "object", "keys": {"status": "string", "errors": "array", "path": "string|null"}}],
170
+ "recommend-workflow": [{"type": "object", "keys": {"status": "string", "errors": "array", "recommended": "object|null", "selected": "object|null", "selection_mode": "string|null", "reason": "string|null"}}],
171
+ "draft-plan": [{"type": "object", "keys": {"schema_version": "string|null", "status": "string", "errors": "array|null", "path": "string|null", "contract_path": "string|null", "plan_candidate": "object|null", "workflow_contract": "object|null", "recommended": "object|null", "selected": "object|null", "selection_mode": "string|null", "warnings": "array|null"}}],
172
+ "record-review": [{"type": "object", "keys": {"schema_version": "string", "review_run_id": "string", "recorded_at": "string", "state_dir": "string", "manifest_path": "string", "manifest_sha256": "string", "plan_sha256": "string", "policy": "string|null", "gate_status": "string", "active_blocking": "array", "depth_profile": "string", "amendment_ready": "boolean", "findings": "object", "artifacts": "array"}}],
173
+ "review-manifest": [{"type": "object", "keys": {"schema_version": "string", "review_run_id": "string", "state_dir": "string", "policy": "string", "gate_status": "string", "active_blocking": "array", "depth_profile": "string", "amendment_ready": "boolean", "findings": "object", "artifacts": "array"}}],
174
+ "next-step": [{"type": "object", "keys": {"id": "string", "action": "string", "files": "array", "preconditions": "array", "validation": "array", "expected_diff": "array", "evidence_ids": "array", "design_decision_ids": "array|null"}}, {"type": "null"}],
175
+ "claim-step": [{"type": "object", "keys": STEP_EVENT}],
176
+ "amend-step": [{"type": "object", "keys": STEP_EVENT}],
177
+ "complete-step": [{"type": "object", "keys": STEP_EVENT}],
178
+ "block-step": [{"type": "object", "keys": STEP_EVENT}],
179
+ "fail-step": [{"type": "object", "keys": STEP_EVENT}],
180
+ "reclaim-step": [{"type": "object", "keys": STEP_EVENT}],
181
+ "renew-lease": [{"type": "object", "keys": STEP_EVENT}],
182
+ "run": [{"type": "object", "keys": COMMAND_RECEIPT}],
183
+ "record-command": [{"type": "object", "keys": COMMAND_RECEIPT}],
184
+ "record-file-change": [{"type": "array", "items": {"type": "object", "keys": FILE_RECEIPT}}],
185
+ "verify-step": [{"type": "object", "keys": VERIFICATION}],
186
+ "verify-run": [{"type": "object", "keys": VERIFICATION}],
187
+ "aggregate-ledgers": [
188
+ {"type": "object", "keys": {"status": "string", "sources": "array", "collisions": "array", "planned": "object"}},
189
+ {"type": "object", "keys": {"status": "string", "sources": "array", "written": "object"}},
190
+ ],
191
+ "detect-stuck": [{"type": "object", "keys": {"schema_version": "string", "status": "string", "findings": "array", "thresholds": "object", "recorded_at": "string"}}],
192
+ "lint-handoff": [{"type": "object", "keys": {"findings": "array"}}],
193
+ "replay-gates": [{"type": "object", "keys": {"status": "string", "errors": "array", "warnings": "array", "receipts": "array", "recorded": "boolean"}}],
194
+ "runtime-status": [{"type": "object", "keys": {"schema_version": "string|null", "id": "string|null", "status": "string|null", "runtimes": "array|null", "mcp_servers": "array|null", "routes": "array|null", "findings": "array", "runtime_config_sha256": "string|null", "created_at": "string|null"}}],
195
+ "events": [{"type": "array", "items": {"type": "object", "keys": {"timestamp": "string", "type": "string", "step_id": "string|null", "attempt_id": "string|null", "source": "object", "data": "object"}}}],
196
+ "next-action": [{"type": "object", "keys": {"state": "string", "reason": "string", "blocking": "boolean", "command": "string|null", "args": "array|null", "step_id": "string|null", "gate": "string|null", "diagnostics": "array", "resumability": RESUMABILITY}}],
197
+ "finish-step": [{"type": "object", "keys": {"verification_status": "string", "verified": "boolean", "completed": "boolean", "diagnostics": "array"}}],
198
+ "finish-run": [{"type": "object", "keys": {"ok": "boolean", "gates": "array", "stopped_at": "string|null", "diagnostics": "array"}}],
199
+ }
200
+
201
+ _MULTI_VALUE_NARGS = ("*", "+", argparse.REMAINDER)
202
+
203
+
204
+ def build_cli_contract() -> dict:
205
+ def arguments(parser: argparse.ArgumentParser) -> list:
206
+ result = []
207
+ for action in parser._actions:
208
+ if isinstance(action, argparse._SubParsersAction) or action.dest == "help":
209
+ continue
210
+ default = action.default
211
+ if default is argparse.SUPPRESS or callable(default):
212
+ default = None
213
+ required = bool(action.option_strings and action.required) or (
214
+ not action.option_strings
215
+ and action.nargs not in ("?", "*", argparse.REMAINDER)
216
+ )
217
+ result.append({
218
+ "names": action.option_strings or [action.dest],
219
+ "required": required, "nargs": action.nargs, "default": default,
220
+ "choices": list(action.choices) if action.choices is not None else None,
221
+ "takes_value": action.nargs != 0,
222
+ "multiple": action.nargs in _MULTI_VALUE_NARGS
223
+ or isinstance(action, argparse._AppendAction),
224
+ })
225
+ return result
226
+
227
+ commands: dict = {}
228
+
229
+ def walk(parser: argparse.ArgumentParser, prefix: str = "") -> None:
230
+ for action in parser._actions:
231
+ if isinstance(action, argparse._SubParsersAction):
232
+ for name, child in action.choices.items():
233
+ command = f"{prefix} {name}".strip()
234
+ commands[command] = arguments(child)
235
+ walk(child, command)
236
+
237
+ walk(build_parser())
238
+ return {"schema_version": "1.0.0", "commands": commands, "json_outputs": JSON_OUTPUTS}
agentflow/contracts.py ADDED
@@ -0,0 +1,263 @@
1
+ """Shared Agentflow artifact contracts and enums."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Optional
7
+
8
+
9
+ TOOL_VERSION = "0.3.0"
10
+
11
+ PLAN_SCHEMA_VERSION = "1.0.0"
12
+ EVIDENCE_SCHEMA_VERSION = "0.2.0"
13
+ ASSUMPTIONS_SCHEMA_VERSION = "0.2.0"
14
+ AMENDMENTS_SCHEMA_VERSION = "0.2.0"
15
+ CONTEXT_RECEIPTS_SCHEMA_VERSION = "0.2.0"
16
+ FAILURES_SCHEMA_VERSION = "0.2.0"
17
+ DRIFT_REPORT_SCHEMA_VERSION = "1.0.0"
18
+ PROOF_PACK_SCHEMA_VERSION = "1.0.0"
19
+ RUNTIME_CONFIG_SCHEMA_VERSION = "0.3.0"
20
+ RUNTIME_SNAPSHOT_SCHEMA_VERSION = "0.3.0"
21
+ WORKFLOW_CONTRACT_SCHEMA_VERSION = "0.1.0"
22
+ WORKFLOW_PACK_SCHEMA_VERSION = "0.1.0"
23
+ CAPABILITY_RECEIPTS_SCHEMA_VERSION = "0.1.0"
24
+ AGGREGATION_SCHEMA_VERSION = "0.1.0"
25
+
26
+ EXECUTION_CONTRACT_SCHEMA_VERSION = "1.0.0"
27
+ STEP_RUNS_SCHEMA_VERSION = "1.0.0"
28
+ COMMAND_RECEIPTS_SCHEMA_VERSION = "1.0.0"
29
+ FILE_RECEIPTS_SCHEMA_VERSION = "1.0.0"
30
+ VERIFICATION_RUNS_SCHEMA_VERSION = "1.0.0"
31
+
32
+ REVIEW_RUNS_SCHEMA_VERSION = "0.6.0"
33
+ REVIEW_MANIFEST_SCHEMA_VERSION = "1.0.0" # keep in lockstep with review_runner.MANIFEST_SCHEMA_VERSION
34
+
35
+ ARTIFACT_SCHEMA_VERSIONS = {
36
+ "plan-lock": PLAN_SCHEMA_VERSION,
37
+ "evidence": EVIDENCE_SCHEMA_VERSION,
38
+ "assumptions": ASSUMPTIONS_SCHEMA_VERSION,
39
+ "amendments": AMENDMENTS_SCHEMA_VERSION,
40
+ "context-receipts": CONTEXT_RECEIPTS_SCHEMA_VERSION,
41
+ "failures": FAILURES_SCHEMA_VERSION,
42
+ "drift-report": DRIFT_REPORT_SCHEMA_VERSION,
43
+ "proof-pack": PROOF_PACK_SCHEMA_VERSION,
44
+ "runtime-config": RUNTIME_CONFIG_SCHEMA_VERSION,
45
+ "runtime-snapshot": RUNTIME_SNAPSHOT_SCHEMA_VERSION,
46
+ "workflow-contract": WORKFLOW_CONTRACT_SCHEMA_VERSION,
47
+ "capability-receipts": CAPABILITY_RECEIPTS_SCHEMA_VERSION,
48
+ "review-runs": REVIEW_RUNS_SCHEMA_VERSION,
49
+ "aggregation": AGGREGATION_SCHEMA_VERSION,
50
+ }
51
+
52
+ EXECUTION_ARTIFACT_SCHEMA_VERSIONS = {
53
+ "execution-contract": EXECUTION_CONTRACT_SCHEMA_VERSION,
54
+ "step-runs": STEP_RUNS_SCHEMA_VERSION,
55
+ "command-receipts": COMMAND_RECEIPTS_SCHEMA_VERSION,
56
+ "file-receipts": FILE_RECEIPTS_SCHEMA_VERSION,
57
+ "verification-runs": VERIFICATION_RUNS_SCHEMA_VERSION,
58
+ }
59
+
60
+ # Working-state ingestion policy. Historical cross-major proof compatibility is
61
+ # handled only by verify-proof; no generic reader inherits that promise.
62
+ POLICY_EXACT = "exact"
63
+ POLICY_SAME_MAJOR = "same_major"
64
+ POLICY_NONE = "none"
65
+ # Fallback used by validate_schema_version for artifacts outside this table
66
+ # (e.g. the transient task-brief intake); named so the default is a visible
67
+ # decision, not an accident of a .get() call.
68
+ DEFAULT_COMPATIBILITY_POLICY = POLICY_SAME_MAJOR
69
+
70
+ # Every declared artifact is classified explicitly (no comprehension fill), so
71
+ # adding an artifact forces a policy decision here and the guard test in
72
+ # test_schema_contracts can actually fail on an unclassified one.
73
+ ARTIFACT_COMPATIBILITY_POLICIES = {
74
+ "plan-lock": POLICY_SAME_MAJOR,
75
+ "evidence": POLICY_SAME_MAJOR,
76
+ "assumptions": POLICY_SAME_MAJOR,
77
+ "amendments": POLICY_SAME_MAJOR,
78
+ "context-receipts": POLICY_SAME_MAJOR,
79
+ "failures": POLICY_SAME_MAJOR,
80
+ "drift-report": POLICY_SAME_MAJOR,
81
+ "proof-pack": POLICY_NONE,
82
+ "runtime-config": POLICY_SAME_MAJOR,
83
+ "runtime-snapshot": POLICY_SAME_MAJOR,
84
+ "workflow-contract": POLICY_SAME_MAJOR,
85
+ "capability-receipts": POLICY_SAME_MAJOR,
86
+ "review-runs": POLICY_SAME_MAJOR,
87
+ "aggregation": POLICY_SAME_MAJOR,
88
+ "execution-contract": POLICY_EXACT,
89
+ "step-runs": POLICY_SAME_MAJOR,
90
+ "command-receipts": POLICY_SAME_MAJOR,
91
+ "file-receipts": POLICY_SAME_MAJOR,
92
+ "verification-runs": POLICY_SAME_MAJOR,
93
+ }
94
+
95
+ # Derived summaries and transient intake files are intentionally excluded.
96
+ BASE_ARTIFACT_PATHS = {
97
+ "plan-lock": ".agent/plan.lock.json",
98
+ "evidence": ".agent/evidence.jsonl",
99
+ "assumptions": ".agent/assumptions.json",
100
+ "context-receipts": ".agent/context-receipts.jsonl",
101
+ "failures": ".agent/failures.jsonl",
102
+ "amendments": ".agent/amendments.jsonl",
103
+ "drift-report": ".agent/drift-report.json",
104
+ "runtime-config": ".agent/runtime.config.json",
105
+ "runtime-snapshot": ".agent/runtime-snapshots.jsonl",
106
+ "workflow-contract": ".agent/workflow.contract.json",
107
+ "capability-receipts": ".agent/capability-receipts.jsonl",
108
+ "review-runs": ".agent/review-runs.jsonl",
109
+ }
110
+
111
+ EXECUTION_ARTIFACT_PATHS = {
112
+ "execution-contract": ".agent/execution.contract.json",
113
+ "step-runs": ".agent/step-runs.jsonl",
114
+ "command-receipts": ".agent/command-receipts.jsonl",
115
+ "file-receipts": ".agent/file-receipts.jsonl",
116
+ "verification-runs": ".agent/verification-runs.jsonl",
117
+ }
118
+
119
+ ARTIFACT_PATHS = {
120
+ **BASE_ARTIFACT_PATHS,
121
+ **EXECUTION_ARTIFACT_PATHS,
122
+ # "aggregation" lives only here, not in BASE_ARTIFACT_PATHS or
123
+ # EXECUTION_ARTIFACT_PATHS: those two dicts drive aggregate.py's own
124
+ # merge-source path list (_ALL_PATHS) and proof.py's normalized execution
125
+ # hash, and adding aggregation.json to either would fold it into the
126
+ # cross-worktree merge/hash it is itself the output of.
127
+ "aggregation": ".agent/aggregation.json",
128
+ }
129
+
130
+ ADAPTERS = ("claude", "codex", "custom", "go-llm", "openai-compatible")
131
+ ROUTE_POLICIES = (
132
+ "manual_only",
133
+ "prefer_local",
134
+ "prefer_low_cost",
135
+ "prefer_quality",
136
+ "prefer_speed",
137
+ )
138
+ READINESS_CHECKS = ("command_exists", "command_spawn", "http", "http_status", "none")
139
+ # #19: MCP servers are evidence-only. http (the /v1/models capability probe) is
140
+ # runtime-specific; MCP liveness uses http_status.
141
+ MCP_READINESS_CHECKS = ("command_exists", "command_spawn", "http_status", "none")
142
+ MCP_TRANSPORTS = ("http", "sse", "stdio")
143
+ MCP_SERVER_STATUSES = ("configured", "ready", "unavailable")
144
+ RUNTIME_STATUSES = ("configured", "ready", "degraded", "unavailable")
145
+ CAPABILITY_SOURCES = ("declared", "probed", "none")
146
+ EVIDENCE_KINDS = ("command", "file", "log", "review", "runtime", "test", "url", "user")
147
+ CHECK_STATUSES = (
148
+ "failed",
149
+ "not_applicable",
150
+ "not_run",
151
+ "passed",
152
+ "skipped",
153
+ "warning",
154
+ )
155
+ EXECUTION_MODES = ("assistive", "external", "manual", "same_session")
156
+ AUTHORITIES = ("advise", "commit", "edit", "read", "verify")
157
+ RISK_LEVELS = ("low", "medium", "high")
158
+ RISK_POLICIES = ("block", "require-confirmation", "warn")
159
+ REVIEW_GATE_POLICIES = ("block", "ignore", "warn")
160
+ WORKFLOW_REVIEW_DEPTHS = ("deep", "light", "none", "spec_quality", "standard")
161
+ # #74: each workflow review_depth contributes a (review-gate floor, requires-run)
162
+ # policy. Joined over the execution-contract gate at proof time; the floor can only
163
+ # raise strictness, never lower it. ``none`` requires no run but does not suppress a
164
+ # recorded review run's own gate (the join leaves the execution default in place).
165
+ REVIEW_DEPTH_POLICY = {
166
+ "none": ("ignore", False),
167
+ "light": ("warn", False),
168
+ "standard": ("warn", False),
169
+ "spec_quality": ("block", True),
170
+ "deep": ("block", True),
171
+ }
172
+ # Total order over review depths (#71 draft-plan solver, #92 recorded-run depth).
173
+ REVIEW_DEPTH_ORDER = {"none": 0, "light": 1, "standard": 2, "spec_quality": 3, "deep": 4}
174
+
175
+
176
+ def review_depth_rank(depth: str) -> int:
177
+ """Rank a validated review depth (KeyError on an unknown value)."""
178
+ return REVIEW_DEPTH_ORDER[depth]
179
+
180
+
181
+ def recorded_review_depth(depth: Optional[str]) -> str:
182
+ """Resolve a recorded run's ``depth_profile``.
183
+
184
+ Absent (``None``) is a legacy pre-#92 run and ranks as ``deep`` — it
185
+ satisfies every requirement, so old ledgers keep verifying. A present but
186
+ out-of-enum value is malformed review evidence, not a silent ``deep``.
187
+ """
188
+ if depth is None:
189
+ return "deep"
190
+ if depth not in REVIEW_DEPTH_ORDER:
191
+ raise ValueError(f"review depth invalid: {depth!r}")
192
+ return depth
193
+
194
+
195
+ def review_depth_satisfies(recorded: str, required: Optional[str]) -> bool:
196
+ """True when a recorded run depth satisfies a required depth.
197
+
198
+ ``required=None`` means a run is required but no depth floor was declared
199
+ (e.g. an execution-contract-only ``require_review_run``), so any recorded
200
+ run satisfies it. Never treat ``None`` as ``deep`` — that would silently
201
+ raise strictness.
202
+ """
203
+ if required is None:
204
+ return True
205
+ return review_depth_rank(recorded) >= review_depth_rank(required)
206
+
207
+
208
+ CAPABILITY_STATUSES = ("used", "waived")
209
+ CAPABILITY_IDS = (
210
+ "tdd",
211
+ "debugging",
212
+ "security-review",
213
+ "frontend-qa",
214
+ "review-spec",
215
+ "review-quality",
216
+ "strict-verification",
217
+ )
218
+ DEFAULT_COMMAND_TIMEOUT_SECONDS = 600
219
+ COMMAND_DECISIONS = ("allowed", "blocked", "timeout")
220
+ RISK_CATEGORIES = (
221
+ "blocked_path",
222
+ "credential_read",
223
+ "destructive_delete",
224
+ "permission_change",
225
+ "pipe_to_shell",
226
+ "privilege_escalation",
227
+ "write_outside_scope",
228
+ )
229
+ PROVENANCE_VALUES = ("attested", "managed", "observed", "reconstructed")
230
+ TRUSTED_PROVENANCE_VALUES = ("managed", "observed", "reconstructed")
231
+ CHANGE_KINDS = ("added", "deleted", "modified", "renamed")
232
+ DIFF_COMMAND_VERSION = "afhunk-v1"
233
+ HUNK_ATTRIBUTION_POLICIES = ("enforce", "observe", "off")
234
+ UNMAPPED_HUNK_REASONS = ("no_matching_hunk",)
235
+ GATE_KINDS = ("command", "inspection")
236
+ STEP_EVENT_KINDS = (
237
+ "abandoned",
238
+ "blocked",
239
+ "claimed",
240
+ "completed",
241
+ "failed",
242
+ "in_progress",
243
+ "lease_renewed",
244
+ "verified",
245
+ )
246
+ RECEIPT_STORES = ("by_attempt", "content_addressed")
247
+ WRITER_MODELS = ("single_writer",)
248
+ LEASE_POLICIES = ("advisory", "enforce")
249
+ DEFAULT_LEASE_TTL_MINUTES = 30
250
+ DEFAULT_LEASE_GRACE_SECONDS = 30
251
+ PROVIDER_NEUTRAL_DENYLIST = (
252
+ "browser tool",
253
+ "chatgpt",
254
+ "claude",
255
+ "codex",
256
+ "mcp",
257
+ "provider function",
258
+ "superpowers",
259
+ )
260
+
261
+
262
+ def strict_mode(cli_strict: bool = False) -> bool:
263
+ return cli_strict or os.environ.get("AGENTFLOW_STRICT") == "1"