esaa-core 0.5.0b1__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.
Files changed (52) hide show
  1. audit/amend_claude_md.py +35 -0
  2. audit/bump_version.py +40 -0
  3. audit/contract_consistency.py +149 -0
  4. audit/critical_findings.py +269 -0
  5. audit/document_future_templates.py +46 -0
  6. audit/eventstore_integrity.py +73 -0
  7. audit/rename_parcer.py +42 -0
  8. audit/schema_conformance.py +64 -0
  9. audit/traceability_and_report.py +118 -0
  10. esaa/__init__.py +17 -0
  11. esaa/__main__.py +4 -0
  12. esaa/adapters/__init__.py +5 -0
  13. esaa/adapters/base.py +17 -0
  14. esaa/adapters/http_llm.py +63 -0
  15. esaa/adapters/mock.py +103 -0
  16. esaa/bootstrap.py +72 -0
  17. esaa/cli.py +571 -0
  18. esaa/compat.py +27 -0
  19. esaa/conflicts.py +45 -0
  20. esaa/constants.py +39 -0
  21. esaa/dispatch.py +187 -0
  22. esaa/errors.py +19 -0
  23. esaa/file_effects.py +388 -0
  24. esaa/metrics.py +131 -0
  25. esaa/plugins.py +666 -0
  26. esaa/projector.py +340 -0
  27. esaa/reject_codes.py +201 -0
  28. esaa/runner_metrics.py +114 -0
  29. esaa/runtime_policy.py +152 -0
  30. esaa/scenarios.py +172 -0
  31. esaa/service.py +2767 -0
  32. esaa/snapshot.py +159 -0
  33. esaa/state_machine.py +108 -0
  34. esaa/store.py +254 -0
  35. esaa/templates/AGENT_CONTRACT.yaml +312 -0
  36. esaa/templates/ORCHESTRATOR_CONTRACT.yaml +285 -0
  37. esaa/templates/RUNTIME_POLICY.yaml +36 -0
  38. esaa/templates/agent_result.schema.json +254 -0
  39. esaa/templates/agents_swarm.yaml +49 -0
  40. esaa/templates/esaa-plugin.schema.json +65 -0
  41. esaa/templates/issues.schema.json +192 -0
  42. esaa/templates/lessons.schema.json +174 -0
  43. esaa/templates/roadmap.schema.json +363 -0
  44. esaa/utils.py +29 -0
  45. esaa/validator.py +122 -0
  46. esaa/vocabulary.py +47 -0
  47. esaa_core-0.5.0b1.dist-info/METADATA +942 -0
  48. esaa_core-0.5.0b1.dist-info/RECORD +52 -0
  49. esaa_core-0.5.0b1.dist-info/WHEEL +5 -0
  50. esaa_core-0.5.0b1.dist-info/entry_points.txt +2 -0
  51. esaa_core-0.5.0b1.dist-info/licenses/LICENSE +21 -0
  52. esaa_core-0.5.0b1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """FIX-1541 — Amenda CLAUDE.md substituindo 'enforcement.mode=reject' por '{reject, require_field, require_step}'.
3
+
4
+ LES-0003 tem enforcement.mode=require_field, entao a redacao original (`mode=reject`)
5
+ e estreita demais. Esta amenda reflete o conjunto real de modos enforcaveis.
6
+ """
7
+ from __future__ import annotations
8
+ import argparse
9
+ from pathlib import Path
10
+
11
+
12
+ def main() -> int:
13
+ ap = argparse.ArgumentParser()
14
+ ap.add_argument("--root", default=".")
15
+ args = ap.parse_args()
16
+ target = Path(args.root) / ".claude/CLAUDE.md"
17
+ if not target.exists():
18
+ print("CLAUDE.md not found; skipping")
19
+ return 0
20
+ txt = target.read_text(encoding="utf-8")
21
+ old = "Trate cada lesson com `enforcement.mode=reject` como **constraint inviolável**"
22
+ new = "Trate cada lesson com `enforcement.mode` em {`reject`, `require_field`, `require_step`} como **constraint inviolável**"
23
+ if old in txt:
24
+ txt = txt.replace(old, new)
25
+ target.write_text(txt, encoding="utf-8")
26
+ print("amended")
27
+ elif new in txt:
28
+ print("already amended")
29
+ else:
30
+ print("trecho nao encontrado; revise manualmente")
31
+ return 0
32
+
33
+
34
+ if __name__ == "__main__":
35
+ raise SystemExit(main())
audit/bump_version.py ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ """FIX-1551 — Atualiza arquivos fora da boundary impl (pyproject.toml, roadmap.schema.json)
3
+ para alinhar a 0.4.1.
4
+ """
5
+ from __future__ import annotations
6
+ import argparse, json, re
7
+ from pathlib import Path
8
+
9
+
10
+ def main() -> int:
11
+ ap = argparse.ArgumentParser()
12
+ ap.add_argument("--root", default=".")
13
+ args = ap.parse_args()
14
+ root = Path(args.root)
15
+
16
+ py = root / "pyproject.toml"
17
+ if py.exists():
18
+ txt = py.read_text(encoding="utf-8")
19
+ txt2 = re.sub(r'(?m)^version\s*=\s*"0\.4\.0"', 'version = "0.4.1"', txt)
20
+ if txt != txt2:
21
+ py.write_text(txt2, encoding="utf-8")
22
+ print("pyproject.toml -> 0.4.1")
23
+ else:
24
+ print("pyproject.toml ja em 0.4.1 ou nao encontrado padrao")
25
+
26
+ rsj = root / ".roadmap/roadmap.schema.json"
27
+ if rsj.exists():
28
+ data = json.loads(rsj.read_text(encoding="utf-8"))
29
+ sv = data.get("properties", {}).get("meta", {}).get("properties", {}).get("schema_version", {})
30
+ if sv.get("const") == "0.4.0":
31
+ sv["const"] = "0.4.1"
32
+ rsj.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
33
+ print("roadmap.schema.json const -> 0.4.1")
34
+ else:
35
+ print(f"roadmap.schema.json const ja em {sv.get('const')}")
36
+ return 0
37
+
38
+
39
+ if __name__ == "__main__":
40
+ raise SystemExit(main())
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """AUD-1001 — Verificador de consistência contrato/documento.
3
+
4
+ Audita drift de versão e divergência de vocabulário entre os artefatos
5
+ canônicos do ESAA. Read-only. Saída: JSON de findings.
6
+
7
+ Uso: python contract_consistency.py --root <repo_root>
8
+ """
9
+ from __future__ import annotations
10
+ import argparse, json, re
11
+ from pathlib import Path
12
+
13
+ # M-04: vocabulario canonico de reject_codes vem de src/esaa/reject_codes.py.
14
+ # Importacao defensiva: se PYTHONPATH=src nao estiver setado, cai para fallback
15
+ # textual (mantem o checker funcional fora do dev environment).
16
+ try:
17
+ import sys as _sys
18
+ _sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "src"))
19
+ from esaa.reject_codes import ALL_CODES as _RC_ALL, WORKFLOW_GATE_CODES as _RC_WG
20
+ DOC_REJECT_CODES = frozenset(_RC_WG)
21
+ ENGINE_ERROR_CODES = frozenset(_RC_ALL)
22
+ except Exception:
23
+ DOC_REJECT_CODES = frozenset({
24
+ "MISSING_CLAIM", "MISSING_COMPLETE", "MISSING_VERIFICATION",
25
+ "PRIOR_STATUS_MISMATCH", "LOCK_VIOLATION", "ACTION_COLLAPSE",
26
+ "IMMUTABLE_DONE_VIOLATION",
27
+ })
28
+ ENGINE_ERROR_CODES = frozenset({
29
+ "SCHEMA_INVALID", "UNKNOWN_ACTION", "WORKFLOW_GATE_VIOLATION", "BOUNDARY_VIOLATION",
30
+ "IMMUTABLE_DONE_VIOLATION", "LOCK_VIOLATION", "TASK_NOT_FOUND",
31
+ "DUPLICATE_TASK", "ISSUE_NOT_FOUND",
32
+ })
33
+
34
+ def main() -> int:
35
+ ap = argparse.ArgumentParser()
36
+ ap.add_argument("--root", default=".")
37
+ args = ap.parse_args()
38
+ root = Path(args.root)
39
+ findings = []
40
+
41
+ def declared_version(path: str, pattern: str) -> str | None:
42
+ p = root / path
43
+ if not p.exists():
44
+ return None
45
+ m = re.search(pattern, p.read_text(encoding="utf-8"))
46
+ return m.group(1) if m else None
47
+
48
+ protocol_versions = {
49
+ "AGENT_CONTRACT.yaml": declared_version(".roadmap/AGENT_CONTRACT.yaml", r'contract_version:\s*"([^"]+)"'),
50
+ "ORCHESTRATOR_CONTRACT.yaml": declared_version(".roadmap/ORCHESTRATOR_CONTRACT.yaml", r'contract_version:\s*"([^"]+)"'),
51
+ "agent_result.schema.json": declared_version(".roadmap/agent_result.schema.json", r'v(0\.\d\.\d)'),
52
+ "constants.py(SCHEMA_VERSION)": declared_version("src/esaa/constants.py", r'SCHEMA_VERSION\s*=\s*"([^"]+)"'),
53
+ "roadmap.schema.json(const)": declared_version(".roadmap/roadmap.schema.json", r'"const":\s*"(0\.\d\.\d)"'),
54
+ }
55
+ package_version = declared_version("pyproject.toml", r'version\s*=\s*"([^"]+)"')
56
+ distinct = {v for v in protocol_versions.values() if v}
57
+ if len(distinct) > 1:
58
+ findings.append({
59
+ "id": "R3", "severity": "high",
60
+ "title": "Version drift entre contratos e engine/projeções",
61
+ "evidence": protocol_versions,
62
+ "recommendation": "Migrar contratos, schemas e constants.py para a mesma versao de protocolo.",
63
+ })
64
+ if package_version and not package_version.startswith("0.5.0"):
65
+ findings.append({
66
+ "id": "R-PACKAGE-VERSION",
67
+ "severity": "low",
68
+ "title": "Versao do pacote nao esta na linha publica beta esperada",
69
+ "evidence": {"pyproject.toml": package_version, "protocol_versions": protocol_versions},
70
+ "recommendation": "Manter pacote 0.5.0b1 enquanto o protocolo permanece em 0.4.1.",
71
+ })
72
+
73
+ # Convenção de nome dos PARCER profiles
74
+ roadmap_dir = root / ".roadmap"
75
+ if roadmap_dir.exists():
76
+ profiles = [p.name for p in roadmap_dir.glob("PARCER_PROFILE*")]
77
+ dot = [n for n in profiles if n.startswith("PARCER_PROFILE.")]
78
+ underscore = [n for n in profiles if n.startswith("PARCER_PROFILE_")]
79
+ if dot and underscore:
80
+ findings.append({
81
+ "id": "R10", "severity": "low",
82
+ "title": "Convencao de nome de PARCER profile inconsistente (ponto vs underscore)",
83
+ "evidence": {"dot": dot, "underscore": underscore},
84
+ "recommendation": "Padronizar para um unico separador.",
85
+ })
86
+
87
+ # FIX-1812: runner.metrics deve estar em todas as fontes canonicas
88
+ rm_sources = {
89
+ "AGENT_CONTRACT.yaml(reserved)": "runner.metrics" in (root / ".roadmap/AGENT_CONTRACT.yaml").read_text(encoding="utf-8") if (root / ".roadmap/AGENT_CONTRACT.yaml").exists() else False,
90
+ "ORCHESTRATOR_CONTRACT.yaml(reserved)": "runner.metrics" in (root / ".roadmap/ORCHESTRATOR_CONTRACT.yaml").read_text(encoding="utf-8") if (root / ".roadmap/ORCHESTRATOR_CONTRACT.yaml").exists() else False,
91
+ "constants.py(CANONICAL_ACTIONS)": "runner.metrics" in (root / "src/esaa/constants.py").read_text(encoding="utf-8") if (root / "src/esaa/constants.py").exists() else False,
92
+ "AGENTS.md": "runner.metrics" in (root / "AGENTS.md").read_text(encoding="utf-8") if (root / "AGENTS.md").exists() else False,
93
+ ".claude/CLAUDE.md": "runner.metrics" in (root / ".claude/CLAUDE.md").read_text(encoding="utf-8") if (root / ".claude/CLAUDE.md").exists() else False,
94
+ }
95
+ missing_rm = [k for k, v in rm_sources.items() if not v]
96
+ if missing_rm:
97
+ findings.append({
98
+ "id": "R-RUNNER-METRICS-DRIFT", "severity": "medium",
99
+ "title": "runner.metrics ausente em fonte canonica",
100
+ "evidence": {"missing_in": missing_rm},
101
+ "recommendation": "Adicionar runner.metrics a todas as fontes do vocabulario reservado.",
102
+ })
103
+
104
+ # M-05: prior_status enum drift entre contract YAML e schema JSON
105
+ try:
106
+ import yaml as _yaml
107
+ ag_path = root / ".roadmap/AGENT_CONTRACT.yaml"
108
+ sch_path = root / ".roadmap/agent_result.schema.json"
109
+ if ag_path.exists() and sch_path.exists():
110
+ ag = _yaml.safe_load(ag_path.read_text(encoding="utf-8")) or {}
111
+ sch = json.loads(sch_path.read_text(encoding="utf-8"))
112
+ yaml_allowed = set(
113
+ ag.get("output_contract", {})
114
+ .get("activity_event", {})
115
+ .get("prior_status", {})
116
+ .get("allowed_values", [])
117
+ )
118
+ schema_enum = set(
119
+ sch.get("properties", {})
120
+ .get("activity_event", {})
121
+ .get("properties", {})
122
+ .get("prior_status", {})
123
+ .get("enum", [])
124
+ )
125
+ if yaml_allowed != schema_enum:
126
+ findings.append({
127
+ "id": "R-PRIOR-STATUS-ENUM-DRIFT", "severity": "medium",
128
+ "title": "prior_status enum diverge entre AGENT_CONTRACT.yaml e agent_result.schema.json",
129
+ "evidence": {
130
+ "yaml_allowed_values": sorted(yaml_allowed),
131
+ "schema_enum": sorted(schema_enum),
132
+ "only_in_yaml": sorted(yaml_allowed - schema_enum),
133
+ "only_in_schema": sorted(schema_enum - yaml_allowed),
134
+ },
135
+ "recommendation": "Alinhar AGENT_CONTRACT.yaml#prior_status.allowed_values com agent_result.schema.json enum.",
136
+ })
137
+ except Exception as exc:
138
+ findings.append({
139
+ "id": "R-PRIOR-STATUS-CHECK-FAILED", "severity": "low",
140
+ "title": "Falha ao executar check_prior_status_enum_drift",
141
+ "evidence": {"error": str(exc)[:120]},
142
+ "recommendation": "Verificar manualmente alinhamento entre AGENT_CONTRACT e agent_result.schema.",
143
+ })
144
+
145
+ print(json.dumps({"checker": "contract_consistency", "findings": findings}, indent=2, ensure_ascii=False))
146
+ return 0
147
+
148
+ if __name__ == "__main__":
149
+ raise SystemExit(main())
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ """AUD-1814 — Critical findings audit checker.
3
+
4
+ Detects the architectural findings tracked by the critical-fixes trail:
5
+ - R-RUNNER-METRICS-DRIFT: runner.metrics missing in any canonical source
6
+ - R-NO-BASELINE-LESSON-RESEED: service.init not emitting baseline lessons event
7
+ - R-REVIEW-ROLE-DRIFT: projector forces owner-only review (no _reviewer_role check)
8
+ - R-NON-SERIALIZABLE-APPEND: append_transactional missing from store
9
+ - R-FILE-EFFECT-ARTIFACTS: file_effects module missing
10
+ - R-HOTFIX-VALIDATION: validate_hotfix_request missing
11
+ - R-DONE-PRIOR-STATUS: agent_result.schema prior_status enum lacks 'done'
12
+ - R-ATOMIC-FILE-EFFECTS: file_effects.stage_file_updates missing
13
+
14
+ Usage: python src/audit/critical_findings.py --root <repo_root>
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+
25
+ def _read(p: Path) -> str:
26
+ return p.read_text(encoding="utf-8") if p.exists() else ""
27
+
28
+
29
+ def _yaml_safe_load(p: Path):
30
+ try:
31
+ import yaml
32
+ return yaml.safe_load(_read(p)) or {}
33
+ except Exception:
34
+ return {}
35
+
36
+
37
+ def check_runner_metrics(root: Path) -> list[dict]:
38
+ findings = []
39
+ sources = {
40
+ "AGENT_CONTRACT.yaml": _yaml_safe_load(root / ".roadmap/AGENT_CONTRACT.yaml")
41
+ .get("vocabulary", {}).get("reserved_orchestrator_actions", []),
42
+ "ORCHESTRATOR_CONTRACT.yaml": _yaml_safe_load(root / ".roadmap/ORCHESTRATOR_CONTRACT.yaml")
43
+ .get("roles", {}).get("orchestrator", {}).get("reserved_actions", []),
44
+ "constants.py": "runner.metrics" in _read(root / "src/esaa/constants.py"),
45
+ }
46
+ missing = []
47
+ for name, value in sources.items():
48
+ if name == "constants.py":
49
+ if not value:
50
+ missing.append(name)
51
+ else:
52
+ if "runner.metrics" not in (value or []):
53
+ missing.append(name)
54
+ if missing:
55
+ findings.append({
56
+ "id": "R-RUNNER-METRICS-DRIFT",
57
+ "severity": "medium",
58
+ "title": "runner.metrics missing from canonical sources",
59
+ "evidence": {"missing_in": missing},
60
+ "recommendation": "Add runner.metrics to all reserved-action vocabularies.",
61
+ })
62
+ return findings
63
+
64
+
65
+ def check_baseline_lessons_reseed(root: Path) -> list[dict]:
66
+ src = _read(root / "src/esaa/service.py")
67
+ findings = []
68
+ if "BASELINE_LESSONS" not in src:
69
+ findings.append({
70
+ "id": "R-NO-BASELINE-LESSON-RESEED",
71
+ "severity": "high",
72
+ "title": "service.py lacks BASELINE_LESSONS constant",
73
+ "evidence": {"file": "src/esaa/service.py"},
74
+ "recommendation": "Define BASELINE_LESSONS and emit baseline_reseed event in init.",
75
+ })
76
+ elif "baseline_reseed" not in src:
77
+ findings.append({
78
+ "id": "R-NO-BASELINE-LESSON-RESEED",
79
+ "severity": "high",
80
+ "title": "service.init not emitting baseline_reseed event",
81
+ "evidence": {"file": "src/esaa/service.py", "missing_marker": "baseline_reseed"},
82
+ "recommendation": "Append orchestrator.view.mutate with baseline_reseed in init.",
83
+ })
84
+ return findings
85
+
86
+
87
+ def check_review_role(root: Path) -> list[dict]:
88
+ src = _read(root / "src/esaa/projector.py")
89
+ findings = []
90
+ if "_reviewer_role" not in src:
91
+ findings.append({
92
+ "id": "R-REVIEW-ROLE-DRIFT",
93
+ "severity": "high",
94
+ "title": "projector._apply_review missing role-based authorization",
95
+ "evidence": {"file": "src/esaa/projector.py"},
96
+ "recommendation": "Honor _reviewer_role in payload to allow qa_role mode.",
97
+ })
98
+ return findings
99
+
100
+
101
+ def check_serializable_append(root: Path) -> list[dict]:
102
+ src = _read(root / "src/esaa/store.py")
103
+ findings = []
104
+ if "append_transactional" not in src:
105
+ findings.append({
106
+ "id": "R-NON-SERIALIZABLE-APPEND",
107
+ "severity": "critical",
108
+ "title": "store.append_transactional missing",
109
+ "evidence": {"file": "src/esaa/store.py"},
110
+ "recommendation": "Add lock-then-revalidate-then-write transactional API.",
111
+ })
112
+ return findings
113
+
114
+
115
+ def check_file_effects_module(root: Path) -> list[dict]:
116
+ p = root / "src/esaa/file_effects.py"
117
+ findings = []
118
+ if not p.exists():
119
+ findings.append({
120
+ "id": "R-FILE-EFFECT-ARTIFACTS",
121
+ "severity": "high",
122
+ "title": "src/esaa/file_effects.py missing",
123
+ "evidence": {"expected_file": "src/esaa/file_effects.py"},
124
+ "recommendation": "Implement staging + content-addressed artifacts.",
125
+ })
126
+ return findings
127
+ src = _read(p)
128
+ required = ["stage_file_updates", "commit_staged", "discard_staged",
129
+ "compute_file_metadata", "write_artifact", "verify_artifact",
130
+ "read_artifact", "recover_file_effects"]
131
+ missing = [s for s in required if s not in src]
132
+ if missing:
133
+ findings.append({
134
+ "id": "R-FILE-EFFECT-ARTIFACTS",
135
+ "severity": "medium",
136
+ "title": "file_effects.py missing required functions",
137
+ "evidence": {"missing": missing},
138
+ "recommendation": "Implement: " + ", ".join(missing),
139
+ })
140
+ return findings
141
+
142
+
143
+ def check_hotfix_validation(root: Path) -> list[dict]:
144
+ src = _read(root / "src/esaa/service.py")
145
+ findings = []
146
+ if "validate_hotfix_request" not in src:
147
+ findings.append({
148
+ "id": "R-HOTFIX-VALIDATION",
149
+ "severity": "medium",
150
+ "title": "service.validate_hotfix_request missing",
151
+ "evidence": {"file": "src/esaa/service.py"},
152
+ "recommendation": "Add validate_hotfix_request returning structured codes.",
153
+ })
154
+ return findings
155
+ expected_codes = ["HOTFIX_ISSUE_NOT_FOUND", "HOTFIX_TARGET_NOT_FOUND",
156
+ "HOTFIX_TARGET_NOT_DONE", "HOTFIX_SCOPE_INVALID"]
157
+ missing = [c for c in expected_codes if c not in src]
158
+ if missing:
159
+ findings.append({
160
+ "id": "R-HOTFIX-VALIDATION",
161
+ "severity": "low",
162
+ "title": "validate_hotfix_request missing error codes",
163
+ "evidence": {"missing_codes": missing},
164
+ "recommendation": "Emit all hotfix structured reject codes.",
165
+ })
166
+ return findings
167
+
168
+
169
+ def check_done_in_prior_status(root: Path) -> list[dict]:
170
+ p = root / ".roadmap/agent_result.schema.json"
171
+ findings = []
172
+ if not p.exists():
173
+ return findings
174
+ data = json.loads(_read(p))
175
+ enum = (
176
+ data.get("properties", {})
177
+ .get("activity_event", {})
178
+ .get("properties", {})
179
+ .get("prior_status", {})
180
+ .get("enum", [])
181
+ )
182
+ if "done" not in enum:
183
+ findings.append({
184
+ "id": "R-DONE-PRIOR-STATUS",
185
+ "severity": "high",
186
+ "title": "agent_result.schema prior_status enum lacks 'done'",
187
+ "evidence": {"current_enum": enum},
188
+ "recommendation": "Add 'done' to enable issue.report on done tasks.",
189
+ })
190
+ return findings
191
+
192
+
193
+ def check_plugin_dispatch_parity(root: Path) -> list[dict]:
194
+ src = _read(root / "src/esaa/service.py")
195
+ findings = []
196
+ required = ["tasks_with_planned_plugins", "_accept_agent_output", "task.create"]
197
+ missing = [marker for marker in required if marker not in src]
198
+ if missing:
199
+ findings.append({
200
+ "id": "R-PLUGIN-DISPATCH-DRIFT",
201
+ "severity": "high",
202
+ "title": "run path no longer appears to consume planned plugin tasks",
203
+ "evidence": {"missing_markers": missing, "file": "src/esaa/service.py"},
204
+ "recommendation": "Keep run/eligible on the same tasks_with_planned_plugins view and admit task.create before claim.",
205
+ })
206
+ return findings
207
+
208
+
209
+ def check_dry_run_semantics(root: Path) -> list[dict]:
210
+ src = _read(root / "src/esaa/service.py")
211
+ findings = []
212
+ required = ['"status": "dry_run"', "would_append_events", "simulated_last_event_seq"]
213
+ missing = [marker for marker in required if marker not in src]
214
+ if missing:
215
+ findings.append({
216
+ "id": "R-DRY-RUN-AMBIGUOUS",
217
+ "severity": "medium",
218
+ "title": "dry-run responses may be ambiguous",
219
+ "evidence": {"missing_markers": missing, "file": "src/esaa/service.py"},
220
+ "recommendation": "Return status=dry_run and simulated append metadata for every dry-run command.",
221
+ })
222
+ return findings
223
+
224
+
225
+ CHECKS = [
226
+ ("runner_metrics", check_runner_metrics),
227
+ ("baseline_lessons_reseed", check_baseline_lessons_reseed),
228
+ ("review_role", check_review_role),
229
+ ("serializable_append", check_serializable_append),
230
+ ("file_effects_module", check_file_effects_module),
231
+ ("hotfix_validation", check_hotfix_validation),
232
+ ("done_in_prior_status", check_done_in_prior_status),
233
+ ("plugin_dispatch_parity", check_plugin_dispatch_parity),
234
+ ("dry_run_semantics", check_dry_run_semantics),
235
+ ]
236
+
237
+
238
+ def run_checks(root: Path) -> dict:
239
+ all_findings = []
240
+ by_check = {}
241
+ for name, fn in CHECKS:
242
+ result = fn(root)
243
+ by_check[name] = len(result)
244
+ all_findings.extend(result)
245
+ severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
246
+ all_findings.sort(key=lambda f: severity_order.get(f.get("severity"), 9))
247
+ return {
248
+ "checker": "critical_findings",
249
+ "total_findings": len(all_findings),
250
+ "by_check": by_check,
251
+ "by_severity": {
252
+ sev: sum(1 for f in all_findings if f.get("severity") == sev)
253
+ for sev in severity_order
254
+ },
255
+ "findings": all_findings,
256
+ }
257
+
258
+
259
+ def main() -> int:
260
+ ap = argparse.ArgumentParser()
261
+ ap.add_argument("--root", default=".")
262
+ args = ap.parse_args()
263
+ result = run_checks(Path(args.root))
264
+ print(json.dumps(result, indent=2, ensure_ascii=False))
265
+ return 0 if result["total_findings"] == 0 else 0
266
+
267
+
268
+ if __name__ == "__main__":
269
+ sys.exit(main())
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """FIX-1531 — Cria docs/spec/activity_future_templates.md documentando o arquivo."""
3
+ from __future__ import annotations
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ DOC = """# activity_future_templates.jsonl
8
+
9
+ ## Status: experimental / nao consumido pelo runtime
10
+
11
+ O arquivo `.roadmap/activity_future_templates.jsonl` contem **templates** de
12
+ eventos hipoteticos previstos para evolucao futura do protocolo ESAA. Ele:
13
+
14
+ - **nao e lido** por `esaa.store.parse_event_store`;
15
+ - **nao afeta** projecoes (`roadmap.json`/`issues.json`/`lessons.json`);
16
+ - **nao afeta** `esaa verify` ou `esaa replay`;
17
+ - serve como **referencia de design** para acoes/eventos ainda nao implementados.
18
+
19
+ ## Quando usar
20
+
21
+ Ao propor uma nova acao no vocabulario, adicione um template no arquivo (sem
22
+ event_seq, sem ts) ilustrando a estrutura esperada. Quando implementada, o
23
+ template e movido para o event store canonico com event_seq real, e a entrada
24
+ removida deste arquivo.
25
+
26
+ ## Garantia de nao-contaminacao
27
+
28
+ O runtime so consome `.roadmap/activity.jsonl` (ver `EVENT_STORE_PATH` em
29
+ `src/esaa/constants.py`). Qualquer outro arquivo `.jsonl` em `.roadmap/` e
30
+ ignorado.
31
+ """
32
+
33
+
34
+ def main() -> int:
35
+ ap = argparse.ArgumentParser()
36
+ ap.add_argument("--root", default=".")
37
+ args = ap.parse_args()
38
+ target = Path(args.root) / "docs/spec/activity_future_templates.md"
39
+ target.parent.mkdir(parents=True, exist_ok=True)
40
+ target.write_text(DOC, encoding="utf-8")
41
+ print(f"wrote: {target}")
42
+ return 0
43
+
44
+
45
+ if __name__ == "__main__":
46
+ raise SystemExit(main())
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ """AUD-1201 — Integridade do event store e replay de projecao.
3
+
4
+ Checa monotonicidade/gaps de event_seq, append-only e reprodutibilidade das
5
+ projecoes por replay (em especial lessons.json). Read-only.
6
+ """
7
+ from __future__ import annotations
8
+ import argparse, json
9
+ from pathlib import Path
10
+
11
+ def parse_events(path: Path):
12
+ events = []
13
+ for line in path.read_text(encoding="utf-8").splitlines():
14
+ line = line.strip()
15
+ if line:
16
+ events.append(json.loads(line))
17
+ return events
18
+
19
+ def main() -> int:
20
+ ap = argparse.ArgumentParser()
21
+ ap.add_argument("--root", default=".")
22
+ args = ap.parse_args()
23
+ root = Path(args.root)
24
+ findings = []
25
+
26
+ store = root / ".roadmap/activity.jsonl"
27
+ if not store.exists():
28
+ print(json.dumps({"checker": "eventstore_integrity", "findings": [
29
+ {"id": "ERR", "severity": "high", "title": "event store ausente"}]}, indent=2))
30
+ return 0
31
+ events = parse_events(store)
32
+
33
+ seqs = [e["event_seq"] for e in events]
34
+ if seqs != sorted(seqs):
35
+ findings.append({"id": "R-order", "severity": "high",
36
+ "title": "event_seq fora de ordem", "evidence": {"seqs": seqs}})
37
+ gaps = [i for i in range(1, len(seqs)) if seqs[i] != seqs[i-1] + 1]
38
+ if gaps:
39
+ findings.append({"id": "R-gap", "severity": "high",
40
+ "title": "gaps em event_seq", "evidence": {"positions": gaps}})
41
+
42
+ # lessons reproducibility: lessons so nascem de issue.report(category=process,subtype=lesson)
43
+ lesson_sources = [e for e in events
44
+ if e["action"] == "issue.report"
45
+ and e.get("payload", {}).get("category") == "process"
46
+ and e.get("payload", {}).get("subtype") == "lesson"]
47
+ stored = root / ".roadmap/lessons.json"
48
+ stored_lessons = json.loads(stored.read_text(encoding="utf-8")).get("lessons", []) if stored.exists() else []
49
+ if stored_lessons and not lesson_sources:
50
+ findings.append({
51
+ "id": "R1", "severity": "critical",
52
+ "title": "lessons.json NAO e reconstruivel por replay",
53
+ "evidence": {"stored_lessons": [l["lesson_id"] for l in stored_lessons],
54
+ "lesson_creating_events": 0},
55
+ "recommendation": "Modelar lessons como eventos (issue.report/lesson) ou outra acao versionada; senao 'esaa project' apaga as lessons.",
56
+ })
57
+
58
+ # schema_version misto nos eventos
59
+ ev_versions = sorted({e.get("schema_version") for e in events})
60
+ if len(ev_versions) > 1:
61
+ findings.append({
62
+ "id": "R3b", "severity": "medium",
63
+ "title": "schema_version misto no event store",
64
+ "evidence": {"versions": ev_versions},
65
+ "recommendation": "make_event usa constante fixa e regride eventos novos para a versao antiga.",
66
+ })
67
+
68
+ print(json.dumps({"checker": "eventstore_integrity",
69
+ "events": len(events), "findings": findings}, indent=2, ensure_ascii=False))
70
+ return 0
71
+
72
+ if __name__ == "__main__":
73
+ raise SystemExit(main())
audit/rename_parcer.py ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python3
2
+ """FIX-1521 — Rename PARCER_PROFILE_agent-docs.yaml para PARCER_PROFILE.agent-docs.yaml.
3
+
4
+ Adota convencao '.' (4 dos 5 profiles ja usam). Atualiza referencias textuais
5
+ em CLAUDE.md e AGENTS.md. O evento 7 do event store permanece imutavel com o
6
+ nome antigo (registro historico valido).
7
+
8
+ Uso: python src/audit/rename_parcer.py --root <repo_root>
9
+ """
10
+ from __future__ import annotations
11
+ import argparse, re, shutil
12
+ from pathlib import Path
13
+
14
+
15
+ def main() -> int:
16
+ ap = argparse.ArgumentParser()
17
+ ap.add_argument("--root", default=".")
18
+ args = ap.parse_args()
19
+ root = Path(args.root)
20
+ old = root / ".roadmap/PARCER_PROFILE_agent-docs.yaml"
21
+ new = root / ".roadmap/PARCER_PROFILE.agent-docs.yaml"
22
+ if old.exists() and not new.exists():
23
+ shutil.move(str(old), str(new))
24
+ print(f"renamed: {old.name} -> {new.name}")
25
+ elif new.exists():
26
+ print("already renamed")
27
+ else:
28
+ print("source file not found; skipping")
29
+
30
+ for ref in (root / ".claude/CLAUDE.md", root / "AGENTS.md"):
31
+ if not ref.exists():
32
+ continue
33
+ txt = ref.read_text(encoding="utf-8")
34
+ new_txt = re.sub(r"PARCER_PROFILE_agent-docs\.yaml", "PARCER_PROFILE.agent-docs.yaml", txt)
35
+ if new_txt != txt:
36
+ ref.write_text(new_txt, encoding="utf-8")
37
+ print(f"updated refs in {ref}")
38
+ return 0
39
+
40
+
41
+ if __name__ == "__main__":
42
+ raise SystemExit(main())