docs(spec): FU-8a process→code guards design (GAP-21/22) + split GAP-23 to #69

GAP-21: sync_agents --verify exits non-zero on drift; adapter_type mismatch
counted as drift (loud), not silent skip — makes it an enforceable gate (INV-MC1).
GAP-22: fitness-function pytest guarding against raw Paperclip HTTP + direct
agent_wakeup_requests INSERT (INV-INT1/INT3). Repo pre-scanned: 0 existing
violations → clean forward-fence. Verified vs 3+ sources (architectural fitness
functions; drift-verify non-zero exit). GAP-23 (spec→agents) split to #69.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 10:48:15 +00:00
parent 43873adc90
commit e8431a2adf
2 changed files with 109 additions and 17 deletions

View File

@@ -0,0 +1,78 @@
# FU-8a — מחסומי-תהליך → מחסומי-קוד (Process Barriers → Code Guards) — עיצוב
**סטטוס:** מאושר-לעיצוב · **תאריך:** 2026-05-31 · **ענף:** TBD
**מכסה:** GAP-21, GAP-22 · **מספק:** INV-MC1, INV-INT1, INV-INT3 · **משימה:** TaskMaster #66
**תלוי ב:** — · **סוג:** pure-code · **מחוץ-להיקף:** GAP-23 (חיווט ספ→סוכנים) → #69 / FU-8b.
---
## 1. הבעיה
שני מחסומים שהיום נשענים על **נוהל אנושי** ולא על **קוד**, ולכן ניתנים להפרה שקטה:
- **GAP-21 (INV-MC1):** סנכרון-סוכנים חוצה-חברות (`sync_agents_across_companies.py`) ידני ולא-נאכף.
ב-`--verify` (script:397) הוא **יוצא 0 גם כשיש drift**, ו-adapter_type-mismatch (script:388)
מודפס כ-"SKIPPING" ו**נבלע** — אין סיגנל-כשל שניתן לתלות בו gate.
- **GAP-22 (INV-INT1/INT3):** אין מחסום-קוד נגד עקיפת ה-helpers המאושרים של Paperclip — קריאת
`httpx`/`requests` גולמית ל-API של Paperclip (במקום `web/paperclip_api.pc_request`) או `INSERT`
ישיר ל-`agent_wakeup_requests` (במקום ה-wakeup API). היום זה כלל-נוהל ב-CLAUDE.md/HEARTBEAT בלבד.
## 2. ההכרעה (מאומתת ≥3 מקורות)
**Architectural fitness functions** — הופכים החלטת-ארכיטקטורה מ"הסכמה חברתית" ל**כלל נאכף שנתפס
ברגע ההפרה**, כ-assertion דמוי-טסט ב-CI. שני המחסומים מיושמים ככאלה:
| החלטה | נימוק | מקורות |
|-------|--------|--------|
| GAP-21: `--verify` יוצא **non-zero על drift**; adapter_type-mismatch = **drift** (לא silent skip) + דיווח רם | drift-verify הוא gate רק אם הוא יוצא non-zero (Terraform 0/2); "alert, don't skip silently" | InfoQ fitness-functions; Firefly CI-drift; Spacelift drift |
| GAP-22: **fitness-function (pytest שסורק את ה-repo)**, לא import-linter | הכלל הוא דפוס-*שימוש*/מחרוזת (http ל-URL, מחרוזת-SQL), לא גבול-import; fitness-function כ-test-assertion ב-CI הוא הכלי | InfoQ; Lukas Niessen; aipatternbook |
| לרוץ ב-**חבילת-הטסטים הקיימת** (לא CI חדש) | אין CI-lint בפרויקט (רק deploy.yaml); חבילת ה-pytest היא שער-האיכות הקיים | (נגזר מהמצב) |
## 3. הרכיבים
- **Modify** `scripts/sync_agents_across_companies.py` (GAP-21):
- `--verify` יחזיר **exit 1** כש-`plan` לא-ריק **או** כשיש adapter_type-mismatch (כיום `return` שקט).
- adapter_type-mismatch: נספר ל-`mismatches` ומדווח רם (`❌`), לא רק "SKIPPING"; נכלל בסיגנל-הכשל
של `--verify`. (ה-skip עצמו ב-`--apply` נשמר — לא מסנכרנים adapter_type אוטומטית — אבל `--verify`
**נכשל** כדי לאלץ טיפול ידני.)
- **Create** `mcp-server/tests/test_paperclip_access_guard.py` (GAP-22) — fitness-function שסורק את
עץ-המקור (`web/`, `mcp-server/src/`, `scripts/`, `plugin-legal-ai/` אם רלוונטי) ו**נכשל** אם נמצא:
1. קריאת-HTTP גולמית ל-Paperclip — `httpx`/`requests`/`aiohttp` עם `PAPERCLIP_API_URL` או
`localhost:3100`/`pc.nautilus`**מחוץ** ל-`web/paperclip_api.py` (ה-helper המאושר).
2. `INSERT INTO agent_wakeup_requests` (כל קובץ) — חייב לעבור דרך wakeup API.
3. `curl ... $PAPERCLIP_API_URL` ב-shell — מחוץ ל-`scripts/pc.sh`.
- מימוש: סריקת-טקסט ממוקדת (regex) עם **allowlist** מפורש (הקבצים המאושרים) + הודעת-כשל שמסבירה
את ה-helper הנכון. (AST מלא מיותר — הדפוסים הם מחרוזות-URL/SQL, לא מבנה-קוד.)
- **Create** `scripts/check_paperclip_access.py` (GAP-22, אופציונלי-דק) — wrapper הניתן להרצה ידנית/CI
שמריץ את אותה לוגיקת-סריקה (מייבא מהטסט או חולק helper); exit non-zero על הפרה. (אם הטסט מספיק —
לדלג, YAGNI.)
## 4. שינויי-התנהגות וסיכון
| שינוי | השפעה | סיכון |
|--------|--------|--------|
| `--verify` יוצא 1 על drift | הופך ל-gate שמיש (אפשר לתלות בו cron/CI) | נמוך — לא משנה `--apply`; משנה רק exit-code של verify |
| adapter_type-mismatch רם + נכלל ב-fail | drift של adapter לא נבלע | נמוך — דיווח; ה-skip ב-apply נשמר |
| fitness-function guard | הפרה עתידית תיכשל בטסטים | נמוך — **ה-repo נסרק 2026-05-31: 0 הפרות קיימות** (אין httpx גולמי ל-Paperclip, אין INSERT ל-agent_wakeup_requests, אין curl גולמי). הגדר הוא גדר-קדימה נקי, אפס תיקוני-קוד קיימים |
| allowlist | קבצים מאושרים (paperclip_api.py, pc.sh) פטורים | נמוך — מפורש ומתועד |
## 5. אסטרטגיית בדיקה
- **GAP-22 fitness-function** נבדק על עצמו: הטסט מאתר דפוס-הפרה מוזרק (fixture עם httpx ל-Paperclip)
ומאשר שהוא נתפס; ומאשר שה-helpers המאושרים (paperclip_api.py) **לא** מסומנים. כלומר הטסט בודק
את הסורק על דוגמאות חיוביות+שליליות, ואז מריץ אותו על ה-repo האמיתי (חייב לעבור — אחרת יש הפרה
קיימת לתקן).
- **GAP-21** — בדיקת-יחידה ללוגיקת ה-exit/mismatch: מתוך master/mirror סינתטיים, `--verify` עם drift
מחזיר 1; ללא drift מחזיר 0; adapter_type-mismatch → 1 + הודעה. (refactor של לוגיקת-ההכרעה לפונקציה
טהורה `_verify_exit_code(plan, mismatches)` שניתנת לבדיקה offline בלי DB/Paperclip.)
- חבילה מלאה ירוקה; smoke: `sync...py --verify` מול ה-state הנוכחי (לדווח אם drift קיים).
## 6. סדר-ביצוע
1. בדיקות אדומות: `_verify_exit_code` (GAP-21) + סורק ה-guard על fixtures (GAP-22).
2. GAP-21: refactor `--verify` ל-`_verify_exit_code` + ספירת mismatches + exit 1 + דיווח רם.
3. GAP-22: סורק (`tests/test_paperclip_access_guard.py`) + allowlist; **הרצה על ה-repo** וטיפול בהפרות קיימות (תיקון או allowlist מנומק).
4. (אופציונלי) `scripts/check_paperclip_access.py` אם רוצים הרצה עצמאית.
5. חבילה ירוקה + smoke (`--verify`) + SCRIPTS.md (אם נוסף סקריפט) + PR+merge + TaskMaster #66.
> **GAP-23 (#69)** — חיווט הספ ל-HEARTBEAT/סוכנים — מחוץ-להיקף (משנה התנהגות-ייצור, דורש החלטה).