# 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** l’albero 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 l’albero PinScope, togliere AGPL, o chiedere a Faradworks una licenza commerciale come se fossimo noi l’upstream | **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. L’audit già dice: l’indipendenza **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 l’attribuzione “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 dell’operatore Michele, con dipendenze dichiarate*, **non** “Periscope senza AGPL”. L’albero 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”:** l’intero 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 | — | A–C; **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à) | S–M | **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 è l’unico 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 A–B; **REWRITE** reviewer nativo | L | **C** | | `models.Finding` vs `motore-finding.md` | **REWRITE** IR (compat shim) | M–L | 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 | M–L | **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 all’audit §11 punti 1–4: `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 5–6, 8–9. - 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 A–B) Qui sì si riscrive l’engine 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 l’obiettivo 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 A–D. --- ## 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; l’audit 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)