Fase A identity on PCB HEAD: operator fence, keep BOM/SPOF/EMI.

Replace Faradworks Inc. TOS/privacy/contact/metadata with Michele Bigi.
Dual-read pinscope_* keys; write periscope_* only. AGPL LICENSE and
GitHub fork parent unchanged. validate.py not edited.
This commit is contained in:
2026-09-20 12:41:17 +02:00
parent 1b3529501f
commit a63e5bd7ab
25 changed files with 1561 additions and 600 deletions
@@ -0,0 +1,289 @@
# Piano — indipendenza architettonica e di licenza da PinScope
**Stato:** solo piano. Nessuna modifica a source di prodotto, remotes, `LICENSE`, né git history.
**Base:** [PINSCOPE_AUDIT.md](./PINSCOPE_AUDIT.md) (snapshot prodotto `facbaa2` / `cursor/pcb-review-plan-44dd`).
**Intento di Michele:** *lasciare* il codice PinScope e le sue licenze; **isolare** lalbero ereditato; disegnare dipendenze; marcare cosa **deve** essere riscritto. Non cancellare ogni riga ereditata.
Questo piano **non** stacca il fork GitHub (`manvalan/periscope``Faradworks/Pinscope`). Lo stacco è un passo legale successivo, fuori da queste fasi di lavoro, salvo decisione esplicita.
---
## 1. Obiettivo
Due indipendenze distinte, nello stesso prodotto:
| Asse | Cosa significa | Cosa *non* significa |
| --- | --- | --- |
| **Architetturale** | Periscope evolve identity, auth, LLM, contratto finding, job PCB/placement **senza** fingere di essere Faradworks/Pinscope e senza che il nuovo codice importi il loop PinScope come se fosse “nostro” | Cancellare `graph.py`, i parser, o il 79% di linee lineage |
| **Licenza / identità** | Operatore, TOS, privacy, metadata, vendor terzi con licenza **nota**; AGPL del fork **resta visibile** e attribuita | Relicenziare lalbero PinScope, togliere AGPL, o chiedere a Faradworks una licenza commerciale come se fossimo noi lupstream |
**Criterio di fatto:** un modulo è “nativo Periscope” se può cambiare contratto (finding, PCB, auth locale, DeepSeek) senza patchare il recinto PinScope. Un modulo è “ereditato” se un `git blame`/Jaccard da audit lo lega a `pinscopex` / UI OSS Faradworks.
Laudit già dice: lindipendenza **non** è bloccata dalle stringhe `pinscope`; è bloccata da pipeline, identità legale, `main` stantio, seam Clerk/Anthropic, ImpedenceFinder senza licenza.
---
## 2. Confine di isolamento (fence)
### 2.1 Recinto — albero ereditato (resta, non si “ripulisce”)
Trattare come **una dipendenza in-tree**, non come prodotto Periscope:
| Pacchetto logico | Path odierni | Licenza da audit |
| --- | --- | --- |
| Core schematico PinScope | `backend/periscopex/{graph,models,parsers,parsers_edif,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}.py` | AGPL-3.0 del fork (blob `LICENSE` identico a upstream) |
| Orchestrazione review | `backend/services/pipeline.py`, `pipeline_worker.py`, `services/validation.py`, `services/extraction.py` (parte DIRECT) | stessa |
| Skills Anthropic/Console | `skills/extract-pattern`, `skills/extract-specs`, `skills/extract-pintable/{schema.json,validate.py}`, `scripts/upload_skills.py`, `backend/skills_manifest.json` | stessa + contratto Claude |
| UI OSS / marketing shell | gran parte di `frontend/src` UPSTREAM/DERIVED, `frontend/public`, `frontend/content/terms.md` + `privacy.md` (testo Faradworks) | AGPL + TOS Faradworks **nel contenuto** |
| Gateway seams | `billing_hook.py`, `proxy.ts`, `clerk-theme-provider.tsx`, `use-optional-auth.ts`, analytics, CSP hosts | stub open-core PinScope |
| Fixture upstream | `simple_project/` (asc/csv/pdf/`design_graph.json`), `docs/how-it-works.svg` | AGPL / contenuto upstream |
**Regola del recinto (quando si implementerà, non ora):**
- Import **solo** da una facciata stretta (es. `build_graph`, `parse_bom`, `parse_netlist_any`, `Finding` Pydantic, `validate_design_async`).
- Nessun nuovo check PCB/schema scrive dentro `validate.py`.
- Nessun file nativo assume Clerk, Anthropic Console, `pinscope.ai`, Faradworks Inc.
- Il recinto **conserva** `LICENSE` AGPL e lattribuzione “derived from Faradworks/Pinscope”.
Implementazione fisica futura (non in fase identità): directory tipo `vendor/pinscope/` **oppure** package `backend/pinscope_legacy/` con shim `periscopex` — solo dopo che i consumer nativi non importano più path sparsi. Non è un rename cosmetico (`pinscopex``periscopex` è già successo e **non** è un recinto).
### 2.2 Nativo Periscope (fuori recinto)
| Area | Path | Perché è nativo |
| --- | --- | --- |
| Contratto finding | `finding_engine.py`, `docs/motore-finding.md` | NEW; clamp FACT/REQUIREMENT/INFERENCE |
| PCB exam | `pcb_pipeline.py`, `pcb_*.py`, `parsers_kicad_pcb.py`, UI `pcb/` | NEW; INDIRECT sul grafo |
| Placement F2 | `placement_pipeline.py`, `placement_pack.py`, `functional_groups.py` | NEW |
| RF / antenna | `antenna_*.py`, parte Impedance UI | NEW |
| Facade Z0 | `periscopex/impedance.py` | NEW; il solver è terzi |
| Auth self-host | `local_jwt.py`, `local_users.py`, `routers/auth.py` | REPLACEMENT |
| LLM DeepSeek | `deepseek_provider.py`, `local_skill.py`, `pdf_ingest.py` | NEW |
| Deploy | `scripts/update-periscope.sh`, `docker-compose.yml` | NEW (nomi `pinscope_*` ancora WEAK) |
| Plugin KiCad | `plugins/kicad/` | NEW |
| Check deterministici fork | `dnp_check`, `sequencing_check`, `layout_rules`, … | NEW ma **INDIRECT**: usano `models` / graph |
### 2.3 Terzi (né PinScope né “nostro motore”)
`vendor/impedancefinder/` — commit `a0c8d0ec`, licenza **UNKNOWN** (audit §6.4). Resta dietro `impedance.py` + `vendor_path.py`. Non mescolare con AGPL PinScope né con il finding engine.
---
## 3. Albero delle dipendenze
Forza: **DIRECT** = stesso control flow / blob lineage PinScope; **INDIRECT** = codice nativo che importa DIRECT; **WEAK** = alias, brand, reti Docker.
```
Faradworks/Pinscope (AGPL-3.0)
├─ DIRECT periscopex.graph / parsers* / taxonomy / models
│ ├─ INDIRECT pipeline MODE=run (Parse → extract → build_graph → review)
│ ├─ INDIRECT pcb_pipeline.ensure_graph + pintable cache
│ ├─ INDIRECT placement / functional_groups
│ └─ INDIRECT cad_bridge, parsers_kicad*, pcb_inventory
├─ DIRECT validate.py + validation_tools + extraction skills
│ ├─ INDIRECT finding_engine.complete_findings (graft, non sostituisce il loop)
│ ├─ INDIRECT pcb AI (_parse_review)
│ └─ WEAK anthropic SDK, upload_skills.py, CLAUDE.md Console
├─ DIRECT models.Finding + normalize_findings + dedupe_findings
│ └─ INDIRECT finding_engine (PE-*, provenance clamp) + FindingCard
├─ DIRECT Next.js app (wizard, report, admin, legal pages)
│ ├─ INDIRECT pcb-exam-section, impedance-panel, topology-panel, local sign-in
│ └─ WEAK Faradworks metadata, pinscopex localStorage, ClerkThemeProvider
├─ DIRECT Clerk / billing_hook seams
│ └─ INDIRECT local JWT (parallel stack, non ha spento Clerk)
└─ (non PinScope) vendor/impedancefinder
└─ INDIRECT periscopex.impedance → tab RF
```
### 3.1 Consumer Periscope × sottosistema PinScope
| Sottosistema PinScope | Consumer Periscope | Forza | Se si riscrive il PinScope… |
| --- | --- | --- | --- |
| Graph + parsers BOM/netlist | `pipeline.py`, PCB, placement, tutti i check | DIRECT / INDIRECT | Cade tutto il prodotto |
| `validate.py` review loop | report schema, PCB AI | DIRECT | Serve un reviewer nativo **prima** di spegnere PinScope |
| `Finding` Pydantic | `finding_engine`, UI card, cad-bridge | DIRECT | Mappare al contratto `motore-finding.md` |
| Skills pintable/pattern/specs | extraction, library | DIRECT | Già in parte sostituiti da `local_skill.py` |
| UI shell | ogni pagina | DIRECT | Identità e finding card prima; wizard dopo |
| Clerk/billing | `main.py`, admin, email | DIRECT + WEAK | Auth locale già c’è |
| Legal Faradworks | `/terms` `/privacy` layout | DIRECT contenuto | Non è codice motore |
**Cosa deve essere riscritto** (per indipendenza vera, fasi tarde — non per “pulizia”):
1. **Identità legale/UI** — obbligatorio e **primo**; non è il motore.
2. **Seam Clerk + Anthropic** — obbligatorio per non dipendere dal cloud Faradworks.
3. **Loop `validate.py` / `_parse_review` / tool graph** — obbligatorio per un motore *Periscope*, **dopo** identità; oggi `finding_engine` è solo un clamp.
4. **IR `Finding`** — allineare il tipo Pydantic al contratto FACT/REQUIREMENT/INFERENCE (oggi campi doppi `finding`/`why`).
5. **UI report/wizard** — quando il contratto finding è nativo; non nella fase identità.
6. **Parsers/graph***non* obbligatori per identità; rewrite solo se si vuole un CAD layer Periscope senza AGPL PinScope (programma XL, ultimo).
---
## 4. Mappa licenze — cosa blocca una licenza Periscope “pulita”
“Pulita” = *Periscope come opera delloperatore Michele, con dipendenze dichiarate*, **non** “Periscope senza AGPL”. Lalbero PinScope **resta AGPL**; toglierlo richiederebbe rewrite totale del recinto (fase ultima) **e** comunque rispetto dei diritti sul codice già pubblicato.
| Pezzo | Evidenza audit | Blocca identità Periscope? | Blocca relicenza non-AGPL del binario combinato? |
| --- | --- | --- | --- |
| `LICENSE` AGPL-3.0 identico a upstream | KEEP | No, se attribuito | **Sì**, finché il recinto è nel tree/servizio |
| TOS/privacy Faradworks, Inc. + `dev@faradworks.com` | REPLACE | **Sì** (operatore sbagliato) | No (non è la GPL) |
| Metadata `layout.tsx` authors/publisher Faradworks, `@getFaradWorks` | REPLACE | **Sì** | No |
| README “commercial licensing of the original → Faradworks” | tenere come *attribuzione upstream*, non come offerta nostra | Parziale | No |
| Fork GitHub ancora `parent: Faradworks/Pinscope` | non staccare in questo piano | No (fatto) | No |
| `anthropic` in `requirements.txt` + `upload_skills.py` | ISOLATE | Policy prodotto / ToS Anthropic vs DeepSeek | No |
| Clerk JWKS / API | ISOLATE | ToS Clerk se usato in prod | No |
| ImpedenceFinder `license: null`, no LICENSE file | INVESTIGATE | Distribuzione RF tab | **Sì** per *quel* vendor (sconosciuta) |
| PyMuPDF dual AGPL *or* Artifex commercial | INVESTIGATE | Quale grant in VPS | AGPL già dal root; commerciale Artifex è altro contratto |
| Transitive Python | no lockfile | Audit incompleto | Possibile gap |
| npm MPL/LGPL (lightningcss, sharp-libvips) | lock fields | Attribuzione frontend | Copyleft diverso, non Faradworks |
| `xlsx@0.18.5` Apache-2.0 nel lock | OK dichiarato | No | No |
**Bloccanti identità (fase A):** testi Faradworks nel prodotto.
**Bloccanti “SaaS Periscope senza AGPL”:** lintero recinto PinScope + PyMuPDF se si usa il grant AGPL. **Questo piano non promette relicenza.**
**Bloccante vendor:** ImpedenceFinder senza licenza — non distribuire come “Periscope MIT/Apache”.
---
## 5. KEEP / ISOLATE / REWRITE / REMOVE
Sforzo: **S** (file/config), **M** (un sottosistema, test esistenti), **L** (contratto + UI + pipeline), **XL** (nuovo CAD/IR). Non è calendario.
| Area | Azione | Sforzo | Fase |
| --- | --- | --- | --- |
| Blob `LICENSE` AGPL + attribuzione PinScope | **KEEP** | — | tutte |
| Graph, parsers, taxonomy, BOM, derating, EDIF | **KEEP** nel recinto | — | AC; **REWRITE** solo E (XL) |
| `simple_project`, how-it-works.svg | **KEEP** | — | |
| Changelog storico Pinscope/`PS-*` | **KEEP** | — | |
| PCB, placement, antenna, plugin, `finding_engine` come clamp | **KEEP** nativo | — | non toccare in A |
| TOS, privacy, contact, footer ©, `layout.tsx` authors, Twitter | **REWRITE** (identità) | SM | **A** |
| `storage-keys.ts` write `pinscopex:` | **REWRITE** write path; dual-read | S | A |
| Alias `pinscope_version`, JWT `pinscope-local`, CORS host, Docker `pinscope_*` | **ISOLATE** poi scadere | S | A |
| Clerk stack | **ISOLATE**; **REMOVE** quando JWT locale è lunico modo | M | B |
| `anthropic`, `upload_skills.py`, CLAUDE.md Console | **ISOLATE**; **REMOVE** se DeepSeek-only | M | B |
| `billing_hook` / proxy stubs | **ISOLATE** (firme); non Faradworks in copy | S | B |
| `validate.py` + `validation_tools` + `_parse_review` | **KEEP** in AB; **REWRITE** reviewer nativo | L | **C** |
| `models.Finding` vs `motore-finding.md` | **REWRITE** IR (compat shim) | ML | C |
| `normalize_findings` / `dedupe_findings` | **KEEP** o reimplementare sul nuovo IR | M | C |
| Skills extraction (schema) | **ISOLATE**; prompt già in parte REPLACEMENT | M | C |
| FindingCard / report UI | **REWRITE** quando IR nativo | ML | **D** |
| Wizard upload / admin Clerkisms | **REWRITE** / pulizia | M | D |
| `vendor/impedancefinder` | **ISOLATE** + licenza esplicita | S legale + M se drop | B (licenza), non engine |
| Parsers/graph nativi Periscope | **REWRITE** solo se si esce da AGPL PinScope | XL | **E** |
| Stacco fork GitHub | **non in questo piano** | legale | dopo E o mai |
**REMOVE** in A: niente codice motore. Solo copy Faradworks *dopo* TOS Periscope pronti.
---
## 6. Sequenza per fasi
### Fase A — Identità e recinto documentale (**non** rewriting del finding engine)
Allineato allaudit §11 punti 14: `finding_engine` / `validate.py` **non** si riscrivono qui.
1. Pubblicare audit + questo piano (solo docs).
2. Decidere `origin/main` vs `cursor/pcb-review-plan-44dd` **senza** staccare il fork (merge prodotto su `main` *oppure* `main` dichiarato archivio).
3. TOS/privacy/contact/metadata/Twitter/footer operatore Periscope; una riga di attribuzione AGPL PinScope/Faradworks (non “siamo Faradworks”).
4. Compatibility fence: un modulo/doc per chiavi `pinscope_*`; smettere di **scrivere** `pinscopex:` in `localStorage`.
5. Dichiarare a codice (commento/ADR) il recinto §2 — ancora senza muovere file.
**Done quando:** UI e legal non dicono Faradworks Inc.; AGPL e fork parent invariati; PCB/schema invariati.
### Fase B — Seam runtime (Clerk, Anthropic, vendor license)
Audit §11 punti 56, 89.
- JWT locale default documentato; Clerk dietro seam nominato (`use_clerk`).
- DeepSeek unico provider supportato; Anthropic + Console skills = optional/legacy; CLAUDE.md = README.
- Licenza scritta per ImpedenceFinder **o** non distribuire `vendor/`.
- Lockfile Python per transitive.
**Done quando:** boot produzione non richiede Clerk; nessun path “devi uploadare skill Anthropic”; vendor RF ha SPDX o è fuori tree.
### Fase C — Motore review PinScope-derived (**rewrite map**, dopo AB)
Qui sì si riscrive lengine ereditato. Ordine interno:
| Step | Oggetto | Perché |
| --- | --- | --- |
| C0 | Spec freeze `motore-finding.md` = unico IR | Già nativo; non diluire in `validate.py` |
| C1 | Adapter: `validate.py` emette solo `Finding` grezzi → `complete_finding` | Già parziale; chiudere i campi doppi |
| C2 | **REWRITE** loop per-IC: tool `find_connected_components` / `get_pintable` / excerpt su provider DeepSeek **senza** copiare `validation_tools.py` | Cuore DIRECT da sostituire |
| C3 | Extraction: `local_skill.py` + schemi JSON (KEEP schema se identici; REWRITE orchestrazione Anthropic) | |
| C4 | Spegnere import da `validate.py` nel PCB (`_parse_review` → parser finding nativo) | Toglie INDIRECT PCB→PinScope reviewer |
| C5 | Test golden `simple_project` + Emmaforo: parity FACT/REQUIREMENT, non parity prose | |
**Non in C:** riscrivere `graph.py` / parser KiCad.
**Done quando:** `validate.py` PinScope può restare nel recinto **non chiamato** dal job schema/PCB, oppure ridotto a wrapper deprecato.
### Fase D — UI
FindingCard, filtri Layout, wizard, admin senza Clerk copy, `package-lock` version drift. Dopo C1 almeno (stesso oggetto finding).
### Fase E — Parsers / graph (opzionale, XL)
Solo se lobiettivo diventa *opera senza codice AGPL PinScope*. Sostituire BOM/netlist/graph/taxonomy è un secondo prodotto. PCB nativo resta INDIRECT finché `ensure_graph` chiama PinScope.
**Stacco fork GitHub:** solo dopo consiglio legale, **non** in AD.
---
## 7. Rischi e sconosciuti
| Rischio | Impatto |
| --- | --- |
| Clone di default `main` ≠ prodotto 2.32.1 | Ogni PR “solo docs” su `main` descrive un altro tree |
| Rewrite `validate.py` in A | Rompe schema+PCB; laudit lo vieta come primo passo |
| Recinto solo rename di cartella | Falso recinto (già successo con `periscopex`) |
| TOS Periscope scritti male | Resta il testo Faradworks *o* si perde AGPL attribution |
| ImpedenceFinder | Distribuzione RF senza grant |
| PyMuPDF grant in VPS | Dual license; non verificato a runtime |
| Clerk rimasto in `ENVIRONMENT=production` | Deploy script vs JWT locale |
| Anthropic ancora importabile se c’è la key | README ≠ runtime |
| Trademark Pinscope/Periscope/Faradworks | Audit: solo *use*, nessuna registrazione |
| Relicenza “Periscope MIT” | Impossibile finché il recinto è servito (AGPL §13 network) |
| Gateway privato Faradworks | Non è nel repo; non auditabile |
---
## 8. Non-obiettivi (espliciti)
- Non modificare source prodotto, remotes, `LICENSE`, git history **in questa consegna** (piano only).
- Non staccare il fork GitHub in queste fasi (**passo legale successivo**, se mai).
- Non cancellare il codice PinScope “perché è PinScope”.
- Non riscrivere `finding_engine` / `validate.py` / PCB nella **fase A**.
- Non auto-placer, non write-back pcbnew (già fuori da `piano-pcb-review.md`).
- Non promettere una licenza Periscope non-AGPL.
- Non mescolare mbparks/pinscope (Arduino) — progetto altro.
- Non usare questo piano come licenza a refactor su `cursor/pcb-review-plan-44dd` oltre un eventuale **solo** file docs su **nuovo** branch.
---
## 9. Priorità di rewrite (lista operativa)
1. **Identità** — TOS, privacy, contact, `layout.tsx`, `site.ts` Twitter, footer ©
2. **Fence compat** — stop write `pinscopex:`; isolare alias versione/JWT/CORS/Docker
3. **`main` vs product branch** — topologia git, fork attaccato
4. **Clerk seam** — default JWT locale
5. **Anthropic/Console seam** — DeepSeek-only di fatto
6. **Licenza ImpedenceFinder** — grant o drop vendor
7. **Lockfile Python** — transitive
8. **IR finding**`Finding``motore-finding.md` (fase C, non A)
9. **Loop `validate.py` / `validation_tools`** — reviewer nativo (C)
10. **PCB/schema smettono di chiamare `_parse_review` PinScope** (C4)
11. **UI FindingCard/wizard** (D)
12. **Parsers/graph** solo se si vuole uscire da AGPL PinScope (E)
13. **Stacco fork** — legale, fuori piano
---
## 10. Riferimenti
- Audit: [PINSCOPE_AUDIT.md](./PINSCOPE_AUDIT.md)
- Contratto finding nativo: [`docs/motore-finding.md`](../motore-finding.md) (store) / `docs/motore-finding.md` nel repo prodotto
- Job PCB: [`docs/piano-pcb-review.md`](../piano-pcb-review.md)