chore(ci): שער-CI ל-INV-INT9 — issues.update({status}) רק במסלול sync-case-status (legal-ai #618)
All checks were successful
INV-INT9 issue.status write-ownership / int9-guard (pull_request) Successful in 10s

ל-`issue.status` כותב לגיטימי אחד בפלאגין: הג'וב `sync-case-status`
(`ctx.jobs.register("sync-case-status", …)` ב-`src/worker.ts`). עד היום
האכיפה הייתה code-review בלבד — וזה בדיוק מה שאפשר את הבאג של legal-ai
‏#446 (flip-flop `done → in_progress`), כותב שני שנכנס בלי שאיש שם לב.

`scripts/int9-guard.mjs` — Node ESM חסר-תלויות. טוקנייזר תו-תו
(מחרוזות/תבניות עם `${}` מקונן/הערות/רגקס-ליטרלים) בונה מסכת "קוד רגיל",
ומעליה: כל `jobs.register("sync-case-status", …)` מגדיר **טווח-תווים מותר**
(איזון-סוגריים), וכל `issues.update(…)` שרשימת-הארגומנטים שלו מכילה את
המזהה `status` חייב לשבת בתוכו.

הגרעיניות היא טווח-קוד ולא רשימת-קבצים (הדפוס של `leak_guard.py` ב-legal-ai)
במכוון: הקריאה המותרת היחידה וכל קריאה עתידית אסורה יושבות באותו
`worker.ts`. הטווח נגזר מהמבנה האמיתי, ולכן שורד מעבר של הג'וב לקובץ אחר —
ושינוי שם-הג'וב מפיל את השער בקול במקום לפתוח חור שקט.

חריגה מכוונת: `// noqa: INT9 — <נימוק>`. `noqa: INT9` בלי נימוק הוא הפרה
בעצמו — אין השתקה שקטה. כשל-פענוח (סוגר שלא נסגר) מפיל את השער ולא נבלע.

`--self-test` מריץ 5 פיקסצ'רים (`scripts/fixtures/int9/`, מחוץ ל-`src/`
בכוונה) ומאמת ספירת-הפרות מדויקת — הוכחה בכל ריצת-CI שהשער נושך.

מגבלה מוצהרת: תיל-מעידה סטטי, לא הוכחה — `issues.update(id, patch)` שבו
אובייקט-העדכון נבנה במקום אחר אינו נתפס.

Refs ezer-mishpati/legal-ai#618

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 04:07:19 +03:00
parent 314745d7a7
commit fa1bda524d
7 changed files with 632 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
name: INV-INT9 issue.status write-ownership
# Hard gate for INV-INT9 (legal-ai docs/spec/X7-paperclip-client-params.md
# §4): `issue.status` has exactly one legitimate writer — the
# `sync-case-status` scheduled job (`ctx.jobs.register("sync-case-status", …)`
# in src/worker.ts), which mirrors legal-ai's case status onto the linked
# Paperclip issue. Built after legal-ai issue #446, where a second, unnoticed
# writer flip-flopped an issue `done → in_progress` — nobody caught it because
# nothing enforced write-ownership. This gate makes sure a third writer never
# lands the same way again: any `ctx.issues.update(...)` call that touches
# `status` outside the `sync-case-status` job fails the build, unless it
# carries an explicit, justified `// noqa: INT9 — <reason>` (a bare noqa with
# no reason is itself a violation — exceptions must be argued in writing).
#
# Pure Node, zero dependencies (scripts/int9-guard.mjs) — no `npm ci` needed.
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
int9-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: INV-INT9 — issue.status write-ownership guard
run: node scripts/int9-guard.mjs
- name: Self-test — הוכחה שהשער נושך (AC5)
run: node scripts/int9-guard.mjs --self-test