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:
@@ -87,4 +87,4 @@ Do not set `ENVIRONMENT=production` unless Clerk auth is configured — that fla
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0, same as upstream Pinscope (Faradworks). For commercial licensing of the original, write to dev@faradworks.com.
|
||||
AGPL-3.0 (see `LICENSE`). This tree is derived from [Faradworks/Pinscope](https://github.com/Faradworks/Pinscope). Faradworks does not operate this instance. For a commercial license of **upstream Pinscope**, write to Faradworks as they publish it (`dev@faradworks.com` historically). For this Periscope instance, contact the operator (Michele Bigi, mikbigi@gmail.com).
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Fase A fence: PinScope identifiers that must be read, never written.
|
||||
|
||||
JWT issue uses JWT_ISSUER. Decode also accepts LEGACY_JWT_ISSUERS.
|
||||
project.json may still contain pinscope_version; new writes use periscope_version.
|
||||
|
||||
See docs/development/PINSCOPE_FENCE.md. Do not import this from validate.py
|
||||
or the finding engine — identity only.
|
||||
"""
|
||||
|
||||
JWT_ISSUER = "periscope-local"
|
||||
LEGACY_JWT_ISSUERS: tuple[str, ...] = ("pinscope-local",)
|
||||
READ_JWT_ISSUERS: tuple[str, ...] = (JWT_ISSUER, *LEGACY_JWT_ISSUERS)
|
||||
|
||||
PROJECT_VERSION_FIELD = "periscope_version"
|
||||
LEGACY_PROJECT_VERSION_FIELD = "pinscope_version"
|
||||
@@ -8,6 +8,7 @@ from typing import Any
|
||||
import jwt
|
||||
|
||||
from backend.config import settings
|
||||
from backend.pinscope_compat import JWT_ISSUER, READ_JWT_ISSUERS
|
||||
|
||||
ALGORITHM = "HS256"
|
||||
TOKEN_TTL_DAYS = 30
|
||||
@@ -21,7 +22,7 @@ def issue_token(user_id: str, email: str) -> str:
|
||||
payload = {
|
||||
"sub": user_id,
|
||||
"email": email,
|
||||
"iss": "periscope-local",
|
||||
"iss": JWT_ISSUER,
|
||||
"iat": now,
|
||||
"exp": now + timedelta(days=TOKEN_TTL_DAYS),
|
||||
}
|
||||
@@ -32,8 +33,8 @@ def decode_token(token: str) -> dict[str, Any] | None:
|
||||
secret = settings.auth_jwt_secret
|
||||
if not secret:
|
||||
return None
|
||||
# Accept pre-rebrand issuer so existing sessions keep working.
|
||||
for issuer in ("periscope-local", "pinscope-local"):
|
||||
# Dual-read PinScope issuer; never write it (see pinscope_compat).
|
||||
for issuer in READ_JWT_ISSUERS:
|
||||
try:
|
||||
return jwt.decode(
|
||||
token,
|
||||
|
||||
@@ -122,7 +122,7 @@ class ProjectMeta(BaseModel):
|
||||
|
||||
# Periscope app version that generated the project's report.
|
||||
# Stamped on the first /start transition and preserved thereafter.
|
||||
# Accept legacy pinscope_version from project.json written before the rebrand.
|
||||
# Dual-read pinscope_version (PinScope fence); serialization uses periscope_version.
|
||||
periscope_version: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("periscope_version", "pinscope_version"),
|
||||
|
||||
@@ -0,0 +1,892 @@
|
||||
# PHASE 0 — PinScope / Periscope forensic audit
|
||||
|
||||
**Status:** observational only. No source, license, git history, remotes, or fork relationship was modified as part of this audit.
|
||||
**Goal:** architectural independence of Periscope from PinScope — not deletion of every inherited line.
|
||||
**Does not implement Phase 1.** Disposition labels are inventory, not a rewrite plan.
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Audit date | 2026-09-20 |
|
||||
| GitHub repo | https://github.com/manvalan/periscope (`fork: true`) |
|
||||
| Declared parent / source | https://github.com/Faradworks/Pinscope |
|
||||
| Product snapshot (working tree described here) | `origin/cursor/pcb-review-plan-44dd` = `facbaa23058be51de3eb2551e074ada270b394d5` |
|
||||
| GitHub default branch | `origin/main` = `ab1c5b081cefb6445b053c170203d6ffe0a88d1b` |
|
||||
| Upstream `main` | `upstream/main` = `b26ad3509f4a19878fb7050aad3da9eacba8d914` (fetched locally for comparison only; **GitHub remotes of the fork were not changed**) |
|
||||
| Merge-base (all three tips) | `b26ad35` — upstream HEAD *is* the fork point |
|
||||
| This document | `docs/development/PINSCOPE_AUDIT.md` (only file this phase is allowed to add) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
Periscope is a **live GitHub fork** of Faradworks/Pinscope (AGPL-3.0). Upstream has **five** commits and has not moved since 2026-07-17. GitHub `main` on the fork is only **three** commits ahead (Docker / OSS / README). The **product** (DeepSeek, rebrand, PCB exam, finding engine, v2.32.1) lives on `cursor/pcb-review-plan-44dd`: **92** commits ahead of upstream, **89** ahead of `origin/main`, **not merged** to the default branch. GitHub lists **no pull requests**.
|
||||
|
||||
On the product snapshot (`facbaa2`, 404 files, 123 544 text lines):
|
||||
|
||||
| Class | Files | File % | Lines | Line % |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| UPSTREAM (identical blob) | 125 | 30.9% | 45 211 | 36.6% |
|
||||
| DERIVED (measurable overlap with an upstream path or rename) | 127 | 31.4% | 52 455 | 42.5% |
|
||||
| REPLACEMENT (same path, Jaccard < 0.20) | 4 | 1.0% | 359 | 0.3% |
|
||||
| NEW (no upstream match at the thresholds below) | 139 | 34.4% | 24 437 | 19.8% |
|
||||
| THIRD_PARTY (`vendor/` except empty `__init__.py` mis-tagged — see §3) | 9 | 2.2% | 1 082 | 0.9% |
|
||||
| UNKNOWN | 0 | — | — | — |
|
||||
|
||||
**PinScope-lineage share (UPSTREAM + DERIVED + REPLACEMENT):** 63.4% of files, 79.4% of lines.
|
||||
**New / vendored share (NEW + THIRD_PARTY):** 36.6% of files, 20.7% of lines.
|
||||
|
||||
Architectural independence is **not** blocked by leftover strings. It **is** blocked by: (1) the still-PinScope pipeline (`periscopex` ← `pinscopex`: graph, parsers, `validate.py`, extraction, Next.js report shell); (2) Faradworks legal/metadata identity still shipping in the UI; (3) GitHub `main` not carrying the product; (4) Clerk / Anthropic seams still in the runtime graph; (5) vendored ImpedenceFinder with **no license file and GitHub `license: null`**.
|
||||
|
||||
Most important to replace **first** (identity and branch topology), not the finding engine or PCB job: Faradworks terms/privacy/contact/metadata; then isolate Clerk and Anthropic; keep parsers/graph as KEEP until a later phase.
|
||||
|
||||
---
|
||||
|
||||
## 2. Method and non-goals
|
||||
|
||||
**Compared trees**
|
||||
|
||||
- Upstream: `git fetch` of `https://github.com/Faradworks/Pinscope.git` into a **local** remote named `upstream` on a disposable clone. This audit did not `git remote set-url`, force-push, rewrite history, or detach the GitHub fork.
|
||||
- Product: `origin/cursor/pcb-review-plan-44dd` (also compared `origin/main` and `origin/cursor/deepseek-71c5`).
|
||||
- File identity: `git ls-tree` blob SHAs.
|
||||
- Text overlap: line-**set** Jaccard on `git show` text. Thresholds (fixed, not guessed): identical SHA → UPSTREAM; same path and Jaccard ≥ 0.20 **or** ≥ 20 shared lines → DERIVED; same path and below that → REPLACEMENT; path only in HEAD with identical SHA to an unmatched upstream blob, or Jaccard ≥ 0.15 against a basename / `pinscopex`→`periscopex` map → DERIVED/REPLACEMENT with `renamed_from`; `vendor/` → THIRD_PARTY; else NEW.
|
||||
- Licenses: root `LICENSE` blob vs upstream; npm `license` field on **every** `frontend/package-lock.json` `packages` entry (827 entries, 0 missing); PyPI JSON `info.license` / `license_expression` / License classifiers for each `backend/requirements.txt` name; ImpedenceFinder GitHub API + tree + `pyproject.toml` + `LICENSE` URL. **No license was inferred from a package name.**
|
||||
- Strings: ripgrep for `Pinscope`, `PinScope`, `pinscope`, `Faradworks`, `faradworks`, `SPDX`, `copyright` (lockfile excluded from hit lists). Matches were classified, not deleted.
|
||||
|
||||
**Classifier caveat (empty blobs):** git empty-file SHA `e69de29` is shared by `backend/__init__.py` (UPSTREAM) and three HEAD empty files. The rename heuristic tagged `backend/periscopex/__init__.py`, `tests/impedancefinder/__init__.py`, and `vendor/impedancefinder/__init__.py` as DERIVED-from-`backend/__init__.py`. That is **not** a meaningful derivation. Corrected intent: package markers NEW / THIRD_PARTY. Counts in §1 are the **raw** classifier (reproducible). Adjusted: DERIVED 124, NEW 141, THIRD_PARTY 10.
|
||||
|
||||
**Non-goals:** no refactors, no PCB/finding-engine edits, no license changes, no history rewrite, no “delete every PinScope line.”
|
||||
|
||||
---
|
||||
|
||||
## 3. Git history map
|
||||
|
||||
### 3.1 Exact upstream and remotes (GitHub, not this clone)
|
||||
|
||||
From `GET https://api.github.com/repos/manvalan/periscope` (2026-09-20):
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| `full_name` | `manvalan/periscope` |
|
||||
| `fork` | `true` |
|
||||
| `parent.full_name` / `source.full_name` | `Faradworks/Pinscope` |
|
||||
| `default_branch` | `main` |
|
||||
| `license.spdx_id` | `AGPL-3.0` (same as parent) |
|
||||
| `created_at` | 2026-07-17T16:23:01Z |
|
||||
| Parent `pushed_at` | 2026-07-17T07:34:32Z |
|
||||
| Fork `pushed_at` | 2026-09-20T07:13:57Z |
|
||||
| Open / listed PRs | `[]` (empty) |
|
||||
|
||||
GitHub still records a **fork of Faradworks/Pinscope**. This audit did not detach it.
|
||||
|
||||
Visible `origin` branches: `main`, `cursor/deepseek-71c5`, `cursor/pcb-review-plan-44dd`. Upstream GitHub has only `main`.
|
||||
|
||||
### 3.2 Upstream commit list (complete — 5 commits)
|
||||
|
||||
| SHA | Date | Author | Subject |
|
||||
| --- | --- | --- | --- |
|
||||
| `6672d2be5744239bd6edfee8abc2ac43d3705f93` | 2026-07-17 | Siddharth Kothari `<Sid@faradworks.com>` | Pinscope open-source core (root commit; extracted from cloud codebase; auth/billing in private gateway) |
|
||||
| `6e83ef9da021ae3a21b76bf38058f034e8dc40d9` | 2026-07-17 | Siddharth Kothari | Remove internal review case log; public support address |
|
||||
| `f89ca0803f571a57703766a154d628def1fe3696` | 2026-07-17 | Siddharth Kothari | README: rewrite for the public launch |
|
||||
| `a8c242933a6917aaeeade383c09f0ea998d467d0` | 2026-07-17 | Siddharth Kothari | README: add pipeline diagram |
|
||||
| `b26ad3509f4a19878fb7050aad3da9eacba8d914` | 2026-07-17 | Sid / GitHub | Update README.md (demo screenshot) |
|
||||
|
||||
`upstream/main` **is** `b26ad35`. Fork `origin/main` contains those five plus three fork-only commits. **0** upstream commits are missing from the fork (ahead/behind `upstream/main...origin/main` = `0 3`).
|
||||
|
||||
### 3.3 Divergence
|
||||
|
||||
```
|
||||
6672d2b Pinscope OSS root (Faradworks)
|
||||
…
|
||||
b26ad35 upstream/main = merge-base
|
||||
├─ origin/main (+3)
|
||||
│ 4888092 Docker, remove Clerk, OSS mode
|
||||
│ 2c5d9d3 Document fork changes in README
|
||||
│ ab1c5b0 Fix Anthropic skill references and backend git support
|
||||
└─ origin/cursor/pcb-review-plan-44dd (+92 from upstream, +89 from origin/main)
|
||||
48246f3 Adapt Pinscope to DeepSeek, auto datasheets, shared library
|
||||
… DeepSeek, KiCad, layout F1, placement, impedance, rebrand 8d2b856,
|
||||
PCB job 16c606a, finding engine dc65e96, … facbaa2 (2.32.1)
|
||||
origin/cursor/deepseek-71c5 is 14 commits behind pcb-review (linear ancestor)
|
||||
```
|
||||
|
||||
`origin/main` does **not** contain DeepSeek, `periscopex`, PCB exam, or the rebrand. A clone of default `main` is still branded Pinscope plus Docker.
|
||||
|
||||
### 3.4 Periscope vs PinScope authorship on product snapshot
|
||||
|
||||
`git log --format='%an <%ae>'` on `origin/cursor/pcb-review-plan-44dd` (97 commits including upstream):
|
||||
|
||||
| Count | Author |
|
||||
| --- | --- |
|
||||
| 88 | Michele Bigi `<mikbigi@gmail.com>` |
|
||||
| 2 | manvalan `<mikbigi@gmail.com>` |
|
||||
| 1 | Michele `<mikbigi@gmail.com>` |
|
||||
| 1 | Cursor Agent `<cursoragent@cursor.com>` |
|
||||
| 3 | Siddharth Kothari `<siddkot@gmail.com>` |
|
||||
| 1 | Siddharth Kothari `<Sid@faradworks.com>` |
|
||||
| 1 | Sid `<44057927+Monacrylic@users.noreply.github.com>` |
|
||||
|
||||
PinScope authors = the five upstream commits. Periscope authors = the 92 fork commits (Michele / manvalan / Cursor Agent).
|
||||
|
||||
### 3.5 Product commit spine (fork-only, oldest → newest groups)
|
||||
|
||||
Not every SHA is repeated here; `git log origin/main..origin/cursor/pcb-review-plan-44dd` is the complete 89-commit list. Grouped:
|
||||
|
||||
1. **DeepSeek / library** (`48246f3` … `30aaf55`): OpenAI-compatible provider, local skills, datasheet fetch, catalog passives, reprocess, quote verify.
|
||||
2. **CAD / KiCad** (`e4fd6c3` … `c5b260b`): hierarchy zip, `.kicad_sch` / `.kicad_pcb` nets, cad-bridge plugin later.
|
||||
3. **Deterministic schematic checks** (`b1ee445` … `7aed64c`): decoupling, I2C, DNP, sequencing, thermal, errata, layout_rules.
|
||||
4. **Impedance / antenna** (`6fc2ac5` … `556306b`): vendored ImpedenceFinder, RF tab.
|
||||
5. **Auth** (`5a69b38`): local multi-user JWT (PinScope-named originally).
|
||||
6. **Layout F1 / Placement F2** (`2950446` … `86121e6`): domains, satellites, gated xy pack.
|
||||
7. **Rebrand** (`8d2b856`, `dc364f5`): Pinscope → Periscope, `pinscopex` → `periscopex`, `PS-*` → `PE-*`.
|
||||
8. **Deploy** (`6b0e44a` … `344ecb3`): `/root/periscope`, Docker networks `pinscope_pinscope` / `pinscope_periscope`.
|
||||
9. **PCB exam + finding engine** (`721ede4` … `facbaa2`): MODE=pcb, shared `library/extracted`, FACT/REQUIREMENT/INFERENCE.
|
||||
|
||||
---
|
||||
|
||||
## 4. File classification vs upstream
|
||||
|
||||
Rules: see §2. Full per-file table: **Appendix A**. Snapshot: 404 HEAD files vs 254 upstream files.
|
||||
|
||||
**Unmatched upstream path still absent from HEAD:** `backend/pinscopex/__init__.py` (package renamed; other `pinscopex/*` files matched as `periscopex/*`).
|
||||
|
||||
**REPLACEMENT (4)** — same path, low overlap:
|
||||
|
||||
| Path | Jaccard | Note |
|
||||
| --- | --- | --- |
|
||||
| `README.md` | 0.133 | DeepSeek/Periscope README vs Pinscope launch README |
|
||||
| `frontend/src/lib/auth.ts` | 0.093 | Local JWT + legacy `pinscope_token` vs open-core stub |
|
||||
| `frontend/src/components/layout/sidebar-auth.tsx` | 0.105 | Local account UI vs Clerk seam stub |
|
||||
| `skills/extract-pintable/SKILL.md` | 0.066 | Prompt rewritten; sibling `schema.json` / `validate.py` still DERIVED |
|
||||
|
||||
**THIRD_PARTY:** `vendor/impedancefinder/*` except `__init__.py` (empty-blob caveat). `SOURCE.md` cites https://github.com/manvalan/ImpedenceFinder commit `a0c8d0ec37c9a1b099082926e50a245778ec8d6e`. Wrapper `backend/periscopex/impedance.py` is NEW. Tests under `tests/impedancefinder/` are NEW (except empty `__init__` mis-tag).
|
||||
|
||||
**NEW (high-signal, not exhaustive):** `finding_engine.py`, PCB/placement/antenna/impedance modules, `pcb_pipeline.py` / `placement_pipeline.py`, DeepSeek/local skill/PDF ingest, local auth routers, KiCad plugin, `docs/motore-finding.md`, `docs/piano-pcb-review.md`, `scripts/update-periscope.sh`, `docker-compose.yml`.
|
||||
|
||||
**DERIVED core:** `backend/periscopex/{graph,models,parsers,parsers_edif,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check}.py` with Jaccard typically 0.63–0.98 vs `backend/pinscopex/*`. `parsers_edif.py`, `pin_function_tokens.py`, `taxonomy.py` are **identical blobs** after rename.
|
||||
|
||||
**UPSTREAM clusters:** `LICENSE` (identical AGPL-3.0 text), most `frontend/src` primitives / marketing assets, `frontend/public`, `skills/extract-pattern` + `extract-specs` trees, most `taxonomy/*.json`, `simple_project` netlist/BOM/PDF/design_graph, several `backend/services` and routers unchanged.
|
||||
|
||||
---
|
||||
|
||||
## 5. Architectural dependency map (PinScope → Periscope)
|
||||
|
||||
Strengths: **DIRECT** = still the same module/control flow; **INDIRECT** = new code that imports DIRECT modules; **WEAK** = naming, docs, compatibility aliases, unused seams; **NONE** = no PinScope type/control coupling observed.
|
||||
|
||||
```
|
||||
PinScope OSS (pinscopex + FastAPI + Next.js + skills)
|
||||
│ DIRECT
|
||||
├─ Design graph / BOM / netlist parsers / taxonomy / derating
|
||||
├─ LLM review loop (validate.py, validation_tools, extraction, pipeline.py)
|
||||
├─ Finding Pydantic model + normalize/dedupe
|
||||
├─ Next.js report / project / admin shell
|
||||
├─ Clerk/billing gateway seams (stubs + remaining calls)
|
||||
└─ Claude Console skills contract (manifest + upload_skills.py)
|
||||
│
|
||||
▼
|
||||
Periscope (rebrand + DeepSeek + extra jobs)
|
||||
│ INDIRECT (new jobs on the same graph/library)
|
||||
├─ MODE=pcb (pcb_pipeline → parsers_kicad_pcb, graph, validate._parse_review, finding_engine)
|
||||
├─ MODE=placement
|
||||
├─ cad-bridge / KiCad plugin
|
||||
├─ finding_engine.complete_finding() wrapping models.Finding
|
||||
└─ datasheet finder / Mouser / local skills (replaces Anthropic upload in practice)
|
||||
│
|
||||
WEAK
|
||||
├─ pinscope_version alias, pinscopex: localStorage, pinscope_token, JWT issuer pinscope-local
|
||||
├─ Docker network names pinscope_*
|
||||
├─ Faradworks terms/privacy/metadata/Twitter
|
||||
└─ CLAUDE.md still describing Console Skills while README forbids Anthropic
|
||||
│
|
||||
NONE (new engines, still may import geometry types)
|
||||
├─ antenna_geometry / antenna_rf
|
||||
└─ vendor ImpedenceFinder (third party) via vendor_path
|
||||
```
|
||||
|
||||
| Subsystem | Strength | Evidence |
|
||||
| --- | --- | --- |
|
||||
| `periscopex.graph` / `parsers*` / `models` / `taxonomy` | DIRECT | Rename + high Jaccard; `pipeline.py` still `build_graph` / `parse_bom` / `parse_netlist_any` |
|
||||
| `periscopex.validate` / `validation_tools` / `services/validation.py` | DIRECT | Same per-IC datasheet review; `complete_findings` grafted at end of validate |
|
||||
| `services/pipeline.py` + `pipeline_worker.py` | DIRECT | Same stage machine; provider swapped |
|
||||
| `finding_engine.py` | INDIRECT | NEW; imports `models.Finding`; seeds `PE-*`; used by `validate.py` and `pcb_checks.py` |
|
||||
| PCB exam (`pcb_pipeline`, `pcb_*`) | INDIRECT | NEW; `ensure_graph` uses PinScope graph; AI path reuses `_parse_review` |
|
||||
| Placement F2 | INDIRECT | NEW on `functional_groups` + graph |
|
||||
| Impedance tab | INDIRECT + THIRD_PARTY | NEW facade; solver from vendor |
|
||||
| Frontend report / wizard | DIRECT / DERIVED | Same App Router tree; FindingCard extended |
|
||||
| Local auth | REPLACEMENT + WEAK | Replaces Clerk for self-host; Clerk still in `middleware/auth.py`, admin, email |
|
||||
| Legal content | DIRECT | `terms.md` / `privacy.md` still Faradworks, Inc. |
|
||||
| `skills/extract-pattern`, `extract-specs` | DIRECT | UPSTREAM blobs |
|
||||
| `skills/extract-pintable/SKILL.md` | REPLACEMENT | Contract (schema/validate) still DERIVED |
|
||||
| `scripts/upload_skills.py` | DIRECT | UPSTREAM; README on product branch says do not run it |
|
||||
| Docker / `update-periscope.sh` | WEAK / NONE | New deploy; leftover `pinscope` host/network names |
|
||||
| `LICENSE` | DIRECT | Identical AGPL-3.0 |
|
||||
|
||||
PCB/finding-engine **code was not modified** for this audit. Observation only: PCB is already a second job, but it is not architecturally independent of PinScope’s graph, pintable cache, or review parser.
|
||||
|
||||
---
|
||||
|
||||
## 6. Package and lockfile licenses
|
||||
|
||||
Root project license: **GNU AGPL v3** text, blob-identical to upstream (`LICENSE` = UPSTREAM). README on the product branch: “AGPL-3.0, same as upstream Pinscope (Faradworks). For commercial licensing of the original, write to dev@faradworks.com.” GitHub API also reports `AGPL-3.0` on both repos. No `SPDX-License-Identifier` headers in the tree (0 files).
|
||||
|
||||
There is **no** Python lockfile (`poetry.lock` / `uv.lock` / `requirements.lock`). Versions below are **declared ranges** plus **PyPI’s current JSON license field** (fetched 2026-09-20), not the exact installed wheel in a given deploy.
|
||||
|
||||
### 6.1 `backend/requirements.txt` (direct)
|
||||
|
||||
| Requirement | PyPI `info.license` (as returned) | Classifiers / notes |
|
||||
| --- | --- | --- |
|
||||
| fastapi | MIT | |
|
||||
| uvicorn | BSD-3-Clause | |
|
||||
| openai | Apache-2.0 | OSI Apache; **added on fork** |
|
||||
| anthropic | MIT | still declared; product README says do not use Anthropic |
|
||||
| google-genai | Apache-2.0 | |
|
||||
| pydantic | MIT | |
|
||||
| pydantic-settings | MIT | OSI MIT |
|
||||
| python-multipart | Apache-2.0 | |
|
||||
| sse-starlette | BSD-3-Clause | |
|
||||
| python-dotenv | BSD-3-Clause | |
|
||||
| openpyxl | MIT | |
|
||||
| google-cloud-storage | Apache-2.0 | |
|
||||
| google-cloud-run | Apache-2.0 | |
|
||||
| google-api-python-client | `Apache 2.0` (PyPI spelling, not SPDX token) | OSI Apache |
|
||||
| pypdf | BSD-3-Clause | |
|
||||
| pymupdf | `Dual Licensed - GNU AFFERO GPL 3.0 or Artifex Commercial License` | **added on fork**; dual license from PyPI, not inferred |
|
||||
| PyJWT | MIT | extras `[crypto]` |
|
||||
| cryptography | Apache-2.0 OR BSD-3-Clause | |
|
||||
| httpx | BSD-3-Clause | **added on fork** |
|
||||
| packaging | Apache-2.0 OR BSD-2-Clause | |
|
||||
| shapely | `BSD 3-Clause` (PyPI spelling) | OSI BSD; **added on fork** (ImpedenceFinder) |
|
||||
| PyYAML | MIT | **added on fork** |
|
||||
|
||||
Transitive Python licenses were **not** enumerated (no lockfile). Marked INVESTIGATE.
|
||||
|
||||
### 6.2 Frontend direct dependencies (`package-lock.json` `packages["node_modules/<name>"]`)
|
||||
|
||||
Lockfile version field is `2.10.0` while `package.json` is `2.26.3` (quality note §8). Every lock `packages` entry has a `license` field (827/827).
|
||||
|
||||
| Package | Locked version | `license` field |
|
||||
| --- | --- | --- |
|
||||
| @base-ui/react | 1.3.0 | MIT |
|
||||
| @xyflow/react | 12.10.2 | MIT |
|
||||
| @types/dagre | 0.7.54 | MIT |
|
||||
| class-variance-authority | 0.7.1 | Apache-2.0 |
|
||||
| clsx | 2.1.1 | MIT |
|
||||
| dagre | 0.8.5 | MIT |
|
||||
| lucide-react | 1.8.0 | ISC |
|
||||
| next | 16.2.3 | MIT |
|
||||
| next-themes | 0.4.6 | MIT |
|
||||
| react / react-dom | 19.2.4 | MIT |
|
||||
| react-markdown | 10.1.0 | MIT |
|
||||
| react-pdf | 10.4.1 | MIT |
|
||||
| shadcn | 4.2.0 | MIT |
|
||||
| tailwind-merge | 3.5.0 | MIT |
|
||||
| tw-animate-css | 1.4.0 | MIT |
|
||||
| xlsx | 0.18.5 | Apache-2.0 |
|
||||
| @tailwindcss/postcss / tailwindcss | 4.2.2 | MIT |
|
||||
| typescript | 5.9.3 | Apache-2.0 |
|
||||
| eslint / eslint-config-next | 9.39.4 / 16.2.3 | MIT |
|
||||
| @types/node | 20.19.39 | MIT |
|
||||
| @types/react / react-dom | 19.2.14 / 19.2.3 | MIT |
|
||||
|
||||
### 6.3 Transitive npm license histogram (lockfile fields only)
|
||||
|
||||
MIT 684; ISC 44; Apache-2.0 43; MPL-2.0 13 (e.g. `axe-core`, `lightningcss*`); BSD-2-Clause 11; LGPL-3.0-or-later 10 (`@img/sharp-libvips-*`); BSD-3-Clause 9; BlueOak-1.0.0 3; Apache-2.0 AND LGPL-3.0-or-later 3; 0BSD 2; plus one each of Apache-2.0 AND LGPL-3.0-or-later AND MIT, Python-2.0 (`argparse`), CC-BY-4.0 (`caniuse-lite`), CC0-1.0, (MIT OR CC0-1.0).
|
||||
|
||||
**Copyleft transitive (frontend):** LGPL-3.0-or-later via sharp/libvips optional platform packages; MPL-2.0 via lightningcss/axe-core. Not removed; recorded.
|
||||
|
||||
### 6.4 Vendored ImpedenceFinder
|
||||
|
||||
| Probe | Result |
|
||||
| --- | --- |
|
||||
| GitHub `license` on `manvalan/ImpedenceFinder` | JSON `license: null` |
|
||||
| `LICENSE` at vendored commit `a0c8d0ec…` | HTTP 404 |
|
||||
| `pyproject.toml` on default `main` | **no** `[project].license` / license-files |
|
||||
| README | **no** license section (Italian functional README) |
|
||||
| Vendored Python headers | describe KiCad formula provenance; **no** SPDX, **no** copyright line |
|
||||
|
||||
License of the vendored snapshot is **UNKNOWN from evidence**. Do not treat as MIT/AGPL by name. INVESTIGATE before any additional distribution story.
|
||||
|
||||
---
|
||||
|
||||
## 7. String search: Pinscope / PinScope / pinscope / Faradworks / SPDX / copyright
|
||||
|
||||
**Do not remove these in Phase 0.** Classification only.
|
||||
|
||||
| Needle | Files | Match lines (approx) | Class of hits |
|
||||
| --- | --- | --- | --- |
|
||||
| `PinScope` | 0 | 0 | none |
|
||||
| `SPDX` | 0 | 0 | none in source |
|
||||
| `copyright` / `Copyright` | 1 (`LICENSE`) | AGPL template + FSF | UPSTREAM license text |
|
||||
| `Pinscope` | 3 | 4 | README license line; changelog rebrand notes; deploy CORS comment |
|
||||
| `pinscope` (any case via listing) | 9 | 30 | legacy IDs, paths, Docker networks, clone URL |
|
||||
| `Faradworks` | 8 | 67 | terms, privacy, marketing, layout metadata, footers |
|
||||
| `faradworks` | 7 | 21 | `dev@faradworks.com`, faradworks.com, `@getFaradWorks` |
|
||||
|
||||
### 7.1 Compatibility / ops (KEEP or ISOLATE — still serving a migration)
|
||||
|
||||
| Location | Hit | Role |
|
||||
| --- | --- | --- |
|
||||
| `backend/services/projects.py` | `pinscope_version` alias | Reads old `project.json` |
|
||||
| `frontend/src/lib/api.ts` | `p.pinscope_version` | Same |
|
||||
| `frontend/src/lib/auth.ts` | `LEGACY_TOKEN_STORAGE_KEY = "pinscope_token"` | Pre-rebrand JWT |
|
||||
| `frontend/src/lib/storage-keys.ts` | still **writes** `pinscopex:*` keys | Comment says migrate; implementation still PinScope prefix |
|
||||
| `scripts/update-periscope.sh` / `docker-compose.yml` | `pinscope_pinscope`, `pinscope.michelebigi.it`, clone `manvalan/pinscope` | Live VPS topology |
|
||||
| `frontend/content/changelog.md` | historical Pinscope / `PS-*` | Documentation of migration |
|
||||
|
||||
### 7.2 Faradworks identity still on the Periscope product UI (REPLACE for independence; not “delete PinScope code”)
|
||||
|
||||
| Location | Hit |
|
||||
| --- | --- |
|
||||
| `frontend/src/app/layout.tsx` | `authors` / `creator` / `publisher`: Faradworks; `url: https://faradworks.com` |
|
||||
| `frontend/src/lib/site.ts` | `TWITTER_HANDLE = "@getFaradWorks"`; `SITE_NAME` already `"Periscope"` |
|
||||
| `frontend/src/components/legal/*` | `© {year} Faradworks` |
|
||||
| `frontend/content/terms.md` | Entire TOS: Faradworks, Inc. Delaware; ownership of “the Service”; `dev@faradworks.com` |
|
||||
| `frontend/content/privacy.md` | Faradworks contact |
|
||||
| `frontend/src/app/(marketing)/page.tsx`, `contact/*` | `dev@faradworks.com` |
|
||||
| `README.md` | commercial license → `dev@faradworks.com` |
|
||||
|
||||
`terms.md` still defines the operator as Faradworks while `SITE_URL` defaults to `https://periscope.michelebigi.it`. That is a **product-identity coupling**, not a parser coupling.
|
||||
|
||||
### 7.3 © in LICENSE
|
||||
|
||||
FSF copyright on the AGPL **license document** itself (2007). That is expected and KEEP. No per-file `Copyright (C) Faradworks` headers were found in `.py`/`.ts` (search limited as in §2). Absence of headers ≠ absence of copyright; the fork inherits AGPL obligations from the parent work.
|
||||
|
||||
---
|
||||
|
||||
## 8. Duplicated concepts and partial migrations
|
||||
|
||||
| Concept | PinScope side (still present) | Periscope side | Migration state |
|
||||
| --- | --- | --- | --- |
|
||||
| Package name | `backend/pinscopex` gone; empty `__init__` only unmatched | `backend/periscopex` | **Done** (rename); imports updated |
|
||||
| Finding IDs | changelog `PS-*` | `PE-*` in finding_engine | **Done** for new IDs; old reports not in repo |
|
||||
| App version field | `pinscope_version` alias | `periscope_version` | **Partial** — dual read |
|
||||
| Browser storage | `pinscopex:` keys still **produced** | comment claims migration | **Partial / inverted** |
|
||||
| Auth | Clerk JWKS, Clerk admin/email, `ClerkThemeProvider` | local JWT `AUTH_JWT_SECRET` | **Partial** — two stacks |
|
||||
| LLM | `anthropic` dep, `upload_skills.py`, CLAUDE.md Console Skills | DeepSeek + `local_skill.py` + README “do not use Anthropic” | **Partial** |
|
||||
| Review findings | `validate._parse_review`, normalize/dedupe | `finding_engine.complete_finding` | **Layered**, not replaced |
|
||||
| Jobs | `MODE=run` analysis | `MODE=pcb`, `MODE=placement` | **Additive** three mutexed jobs |
|
||||
| Default branch vs product | GitHub `main` = Docker Pinscope OSS | pcb-review = 2.32.1 | **Not merged** |
|
||||
| Docker networks | `pinscope_pinscope` (Caddy) | `pinscope_periscope` / aliases | **Partial** rename |
|
||||
| Legal | Faradworks TOS/privacy | SITE_NAME Periscope | **Partial** |
|
||||
| Changelog vs lockfile | `package-lock.json` `"version": "2.10.0"` | `package.json` `2.26.3`, changelog 2.32.1 | **Drift** |
|
||||
| Impedance | (none upstream) | vendor + `periscopex.impedance` | NEW + THIRD_PARTY |
|
||||
| Open-core seams | `billing_hook`, proxy.ts, clerk-theme | OSS no-op + local auth | Stubs KEEP for API shape; identity REPLACE |
|
||||
|
||||
---
|
||||
|
||||
## 9. Technical quality observations (no rewrite proposals)
|
||||
|
||||
These are facts about the snapshot, not a license to rip out PinScope.
|
||||
|
||||
1. **Default branch is not the product.** Anyone cloning `manvalan/periscope` without a branch flag gets pre-DeepSeek Pinscope + Docker (`ab1c5b0`). Live notes refer to 2.32.1 on the PCB branch.
|
||||
2. **Docs disagree:** `CLAUDE.md` (DERIVED, Jaccard 0.67) still titles Periscope but describes Claude Console Skills as required. Product `README.md` (REPLACEMENT) says skills are local and Anthropic must not be used.
|
||||
3. **Finding engine is a clamp, not a new IR.** `validate.py` still produces `Finding` objects; `complete_findings` fills FACT/REQUIREMENT/INFERENCE. Replacing PinScope review in one shot would discard the working schematic loop.
|
||||
4. **PCB job correctly refuses auto-place** (comments in `pcb_pipeline.py`) but **depends** on schematic graph + pintable cache (“run schematic review first”). That is coupling, not a bug by itself.
|
||||
5. **Empty `__init__.py` files** share one git blob; mechanical rename detection over-claims derivation (§2).
|
||||
6. **`frontend/package-lock.json` is almost identical to upstream** (Jaccard 0.999) while app version moved to 2.26.3/2.32.1 — lock **name-version** stale; dependency licenses still readable from the lock.
|
||||
7. **PyMuPDF dual AGPL/commercial** on PyPI is a distribution constraint for binaries/SaaS (AGPL network clause already applies via root LICENSE). Recorded, not changed.
|
||||
8. **ImpedenceFinder** has no license metadata. KiCad formula comments are provenance of **math**, not a license grant.
|
||||
9. **xlsx@0.18.5** lock field is Apache-2.0 (not guessed from “SheetJS”).
|
||||
10. **Auth production guard** still mentions Clerk (`backend/main.py`, `update-periscope.sh` dies if `ENVIRONMENT=production` without Clerk). Local JWT exists but deploy script language is mixed.
|
||||
11. **No per-source-file copyright/SPDX** — harder provenance later; AGPL still applies to the combined work.
|
||||
12. Tests for NEW modules are substantial (`tests/test_pcb_review.py`, `test_finding_engine.py`, etc.) — quality of *additions* is high relative to unmerged `main`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Disposition (KEEP / ISOLATE / REPLACE / REMOVE / INVESTIGATE)
|
||||
|
||||
Independence means Periscope can evolve its product, legal identity, and review contract without pretending to be Faradworks/Pinscope. It does **not** mean deleting `graph.py`.
|
||||
|
||||
| Area | Action | Why |
|
||||
| --- | --- | --- |
|
||||
| AGPL `LICENSE` blob | **KEEP** | Identical upstream license; changing it is out of scope and would not create independence |
|
||||
| Parsers, graph, taxonomy, BOM, derating, EDIF | **KEEP** | DIRECT lineage; this *is* the schematic core; replacing is a later engineering program |
|
||||
| `models.Finding` + normalize/dedupe | **KEEP** + **ISOLATE** | Keep types; route new semantics through `finding_engine` |
|
||||
| `validate.py` LLM loop | **KEEP** for now; **ISOLATE** provider/skills | Working product; independence = provider + finding contract, not a greenfield reviewer |
|
||||
| PCB / placement / antenna / finding_engine / KiCad plugin | **KEEP** | NEW Periscope surface |
|
||||
| `vendor/impedancefinder` | **ISOLATE** + **INVESTIGATE** | Boundary already in `impedance.py`; license unknown |
|
||||
| Faradworks TOS, privacy, footers, layout authors, Twitter, contact email | **REPLACE** | Operator identity; largest *non-engine* coupling |
|
||||
| `pinscope_version` / JWT issuer / CORS host aliases | **ISOLATE** then expire | Needed for live data; not the architecture |
|
||||
| `storage-keys.ts` still writing `pinscopex:` | **REPLACE** (write path) after dual-read | Partial migration bug-shaped |
|
||||
| Clerk seams (`clerk-theme`, JWKS, admin Clerk API, email) | **ISOLATE**; **REMOVE** only when local auth is the only supported mode | Dual stack |
|
||||
| `anthropic` dependency, `upload_skills.py`, Console-oriented CLAUDE.md | **ISOLATE**; **REMOVE** later if DeepSeek-only is policy | README already forbids Anthropic |
|
||||
| GitHub `main` vs pcb-review | **INVESTIGATE** (process) | Merge/publish strategy; do not detach fork |
|
||||
| PyMuPDF dual license + Python transitives | **INVESTIGATE** | No pip lock |
|
||||
| ImpedenceFinder license from author | **INVESTIGATE** | Blocker for *clean* third-party story, not for reading code |
|
||||
| `simple_project` / how-it-works.svg | **KEEP** | UPSTREAM fixtures; rebrand docs separately |
|
||||
| Billing hook / gateway stubs | **KEEP** signatures; **ISOLATE** comments that imply Faradworks cloud | Open-core seam |
|
||||
| PinScope *strings* in changelog | **KEEP** | Historical accuracy |
|
||||
|
||||
**REMOVE** is empty for Phase 0. Nothing should be deleted on the basis of this audit alone.
|
||||
|
||||
---
|
||||
|
||||
## 11. Phase 1 sequence (recommended order; not this change)
|
||||
|
||||
Phase 1 should increase **independence**, not maximize diff against PinScope.
|
||||
|
||||
1. **Land this audit** on a branch that does not rewrite engines (`docs/development/PINSCOPE_AUDIT.md` only).
|
||||
2. **Branch topology:** decide how GitHub `main` relates to `cursor/pcb-review-plan-44dd` without detaching the fork (merge/PR of product onto `main`, or document that `main` is archival). Until then, every clone is the wrong tree.
|
||||
3. **Operator identity:** replace Faradworks TOS/privacy/contact/metadata/Twitter/footers with Periscope operator documents; keep AGPL and a factual “derived from Faradworks/Pinscope” attribution (README already has a form of this).
|
||||
4. **Compatibility fence:** one module/doc for legacy `pinscope_*` keys, JWT issuer, CORS, Docker network names; stop writing new `pinscopex:` storage keys.
|
||||
5. **Auth ISOLATE:** make the self-host path (local JWT) the documented default; keep Clerk behind a named seam so admin/email do not assume Clerk.
|
||||
6. **LLM ISOLATE:** document DeepSeek as the supported provider; treat Anthropic SDK + `upload_skills.py` as optional/legacy so CLAUDE.md matches README.
|
||||
7. **Finding contract:** keep using `finding_engine` as the independence layer on top of `Finding`; do not rewrite `validate.py` in Phase 1.
|
||||
8. **Vendor license:** obtain an explicit license for ImpedenceFinder (or stop distributing the vendor tree until known).
|
||||
9. **Python lock:** add a lockfile so transitive licenses can be audited like npm.
|
||||
10. **Only then** consider isolating parsers/graph behind a narrower Periscope API — a large KEEP surface, not a Phase 1 delete.
|
||||
|
||||
---
|
||||
|
||||
## 12. Unresolved questions and evidence gaps
|
||||
|
||||
1. **Intent of GitHub `main`:** accidental staleness vs deliberate “OSS mirror of upstream + Docker”? No PRs exist to explain it.
|
||||
2. **Legal operator of periscope.michelebigi.it** vs documents that still say Faradworks, Inc. — this audit cannot assign a legal entity.
|
||||
3. **ImpedenceFinder license** (author, KiCad cpp port implications). GitHub API `license` is null.
|
||||
4. **PyMuPDF** AGPL vs Artifex commercial: which grant is used in the VPS deploy?
|
||||
5. **Python transitive licenses** without a lockfile.
|
||||
6. **Whether Anthropic is still reachable** if env keys exist despite README (code path exists; runtime not executed here).
|
||||
7. **Private Faradworks gateway** files mentioned in upstream CLAUDE.md — not in this repo; cannot audit what the parent still replaces in cloud builds.
|
||||
8. **Trademark** “Pinscope” / “Periscope” / “Faradworks”: search found **use**, not a registration database. No legal conclusion.
|
||||
9. **Empty-file DERIVED tags** — see §2; raw vs adjusted counts.
|
||||
10. **`origin` on the analyst clone** included a local `upstream` remote for fetch-only comparison. GitHub fork remotes were not edited; confirm live GitHub still has `parent: Faradworks/Pinscope` after this docs-only PR.
|
||||
11. **mbparks/pinscope** (Arduino) is a **different project**; zero code overlap observed; ignored.
|
||||
12. **Exact installed npm/pip versions on production** (`/root/periscope`) were not inspected; licenses are from lock + PyPI JSON as of 2026-09-20.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — Per-file classification
|
||||
|
||||
Evidence strings are mechanical (SHA / Jaccard / path). Empty-blob rename caveat applies to three `__init__.py` files listed as DERIVED.
|
||||
### UPSTREAM (125)
|
||||
|
||||
| Path | Evidence |
|
||||
| --- | --- |
|
||||
| `.github/workflows/ci.yml` | identical blob SHA |
|
||||
| `LICENSE` | identical blob SHA |
|
||||
| `backend/__init__.py` | identical blob SHA |
|
||||
| `backend/middleware/__init__.py` | identical blob SHA |
|
||||
| `backend/routers/__init__.py` | identical blob SHA |
|
||||
| `backend/routers/deps.py` | identical blob SHA |
|
||||
| `backend/routers/feedback.py` | identical blob SHA |
|
||||
| `backend/routers/survey.py` | identical blob SHA |
|
||||
| `backend/services/__init__.py` | identical blob SHA |
|
||||
| `backend/services/admin_settings.py` | identical blob SHA |
|
||||
| `backend/services/billing_hook.py` | identical blob SHA |
|
||||
| `backend/services/purple_parts.py` | identical blob SHA |
|
||||
| `backend/services/storage.py` | identical blob SHA |
|
||||
| `backend/services/storage_gcs.py` | identical blob SHA |
|
||||
| `backend/services/survey.py` | identical blob SHA |
|
||||
| `docs/how-it-works.svg` | identical blob SHA |
|
||||
| `frontend/.gitignore` | identical blob SHA |
|
||||
| `frontend/AGENTS.md` | identical blob SHA |
|
||||
| `frontend/README.md` | identical blob SHA |
|
||||
| `frontend/components.json` | identical blob SHA |
|
||||
| `frontend/eslint.config.mjs` | identical blob SHA |
|
||||
| `frontend/postcss.config.mjs` | identical blob SHA |
|
||||
| `frontend/public/datasheet.gif` | identical blob SHA |
|
||||
| `frontend/public/derating.png` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/altium.svg` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/autodesk.svg` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/cadence.svg` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/easyeda.svg` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/kicad.svg` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/orcad.svg` | identical blob SHA |
|
||||
| `frontend/public/eda-logos/siemens.svg` | identical blob SHA |
|
||||
| `frontend/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf` | identical blob SHA |
|
||||
| `frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.asc` | identical blob SHA |
|
||||
| `frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.csv` | identical blob SHA |
|
||||
| `frontend/public/faradworks-logo-white.png` | identical blob SHA |
|
||||
| `frontend/public/favicon_io/android-chrome-192x192.png` | identical blob SHA |
|
||||
| `frontend/public/favicon_io/android-chrome-512x512.png` | identical blob SHA |
|
||||
| `frontend/public/favicon_io/apple-touch-icon.png` | identical blob SHA |
|
||||
| `frontend/public/favicon_io/favicon-16x16.png` | identical blob SHA |
|
||||
| `frontend/public/favicon_io/favicon-32x32.png` | identical blob SHA |
|
||||
| `frontend/public/favicon_io/favicon.ico` | identical blob SHA |
|
||||
| `frontend/public/file.svg` | identical blob SHA |
|
||||
| `frontend/public/globe.svg` | identical blob SHA |
|
||||
| `frontend/public/next.svg` | identical blob SHA |
|
||||
| `frontend/public/power-tree.gif` | identical blob SHA |
|
||||
| `frontend/public/report.png` | identical blob SHA |
|
||||
| `frontend/public/vercel.svg` | identical blob SHA |
|
||||
| `frontend/public/window.svg` | identical blob SHA |
|
||||
| `frontend/scripts/sync-version.mjs` | identical blob SHA |
|
||||
| `frontend/src/app/(app)/api/graph/[id]/route.ts` | identical blob SHA |
|
||||
| `frontend/src/app/(app)/api/projects/route.ts` | identical blob SHA |
|
||||
| `frontend/src/app/(app)/api/report/[id]/route.ts` | identical blob SHA |
|
||||
| `frontend/src/app/(marketing)/contact/contact-form.tsx` | identical blob SHA |
|
||||
| `frontend/src/app/(marketing)/layout.tsx` | identical blob SHA |
|
||||
| `frontend/src/app/favicon.ico` | identical blob SHA |
|
||||
| `frontend/src/app/globals.css` | identical blob SHA |
|
||||
| `frontend/src/app/layout.tsx` | identical blob SHA |
|
||||
| `frontend/src/app/robots.ts` | identical blob SHA |
|
||||
| `frontend/src/app/sitemap.ts` | identical blob SHA |
|
||||
| `frontend/src/app/twitter-image.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/analytics/reddit-pixel-match-keys.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/analytics/reddit-pixel.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/billing/credits-context.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/billing/paused-run-banner.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/feedback/feedback-dialog.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/legal/file-guide-page.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/marketing/pricing-section.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/pdf/pdf-viewer-sheet.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/progress/pipeline-stepper.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/report/finding-comments.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/report/mention-input.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/report/reviewed-section.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/report/status-badge.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/theme/clerk-theme-provider.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/theme/theme-provider.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/theme/theme-toggle.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/alert-dialog.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/badge.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/button.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/card.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/collapsible.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/dialog.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/dropdown-menu.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/input.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/label.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/progress.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/scroll-area.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/select.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/separator.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/sheet.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/skeleton.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/tabs.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/textarea.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/toast.tsx` | identical blob SHA |
|
||||
| `frontend/src/components/ui/tooltip.tsx` | identical blob SHA |
|
||||
| `frontend/src/hooks/use-auth-api.ts` | identical blob SHA |
|
||||
| `frontend/src/lib/mock-data.ts` | identical blob SHA |
|
||||
| `frontend/src/lib/utils.ts` | identical blob SHA |
|
||||
| `frontend/src/proxy.ts` | identical blob SHA |
|
||||
| `frontend/tsconfig.json` | identical blob SHA |
|
||||
| `scripts/backfill_passive_subtype.py` | identical blob SHA |
|
||||
| `scripts/clear_rules_from_extractions.py` | identical blob SHA |
|
||||
| `scripts/gc_orphan_blobs.py` | identical blob SHA |
|
||||
| `scripts/migrate_datasheets_to_blobs.py` | identical blob SHA |
|
||||
| `scripts/migrate_datasheets_to_library.py` | identical blob SHA |
|
||||
| `scripts/upload_skills.py` | identical blob SHA |
|
||||
| `simple_project/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf` | identical blob SHA |
|
||||
| `simple_project/TI-MSP-KICAD9-TUTORIAL.asc` | identical blob SHA |
|
||||
| `simple_project/TI-MSP-KICAD9-TUTORIAL.csv` | identical blob SHA |
|
||||
| `simple_project/design_graph.json` | identical blob SHA |
|
||||
| `skills/extract-pattern/SKILL.md` | identical blob SHA |
|
||||
| `skills/extract-pattern/schema.json` | identical blob SHA |
|
||||
| `skills/extract-pattern/validate.py` | identical blob SHA |
|
||||
| `skills/extract-specs/SKILL.md` | identical blob SHA |
|
||||
| `skills/extract-specs/schema.json` | identical blob SHA |
|
||||
| `skills/extract-specs/validate.py` | identical blob SHA |
|
||||
| `taxonomy/connector.json` | identical blob SHA |
|
||||
| `taxonomy/discrete.json` | identical blob SHA |
|
||||
| `taxonomy/fuse.json` | identical blob SHA |
|
||||
| `taxonomy/ic.json` | identical blob SHA |
|
||||
| `taxonomy/switch.json` | identical blob SHA |
|
||||
| `taxonomy/test_point.json` | identical blob SHA |
|
||||
| `taxonomy/transformer.json` | identical blob SHA |
|
||||
| `tests/__init__.py` | identical blob SHA |
|
||||
| `tests/test_cache_breakpoint_limit.py` | identical blob SHA |
|
||||
|
||||
### DERIVED (127)
|
||||
|
||||
| Path | Evidence |
|
||||
| --- | --- |
|
||||
| `.dockerignore` | same path; line-set Jaccard=0.857; shared_lines=12; head_lines=13; upstream_lines=13 |
|
||||
| `.gitignore` | same path; line-set Jaccard=0.963; shared_lines=52; head_lines=54; upstream_lines=52 |
|
||||
| `CLAUDE.md` | same path; line-set Jaccard=0.669; shared_lines=89; head_lines=111; upstream_lines=111 |
|
||||
| `backend/.env.example` | same path; line-set Jaccard=0.236; shared_lines=26; head_lines=77; upstream_lines=59 |
|
||||
| `backend/CLAUDE.md` | same path; line-set Jaccard=0.847; shared_lines=111; head_lines=121; upstream_lines=121 |
|
||||
| `backend/Dockerfile` | same path; line-set Jaccard=0.600; shared_lines=18; head_lines=25; upstream_lines=23 |
|
||||
| `backend/_version.py` | same path; line-set Jaccard=0.812; shared_lines=26; head_lines=29; upstream_lines=29 |
|
||||
| `backend/config.py` | same path; line-set Jaccard=0.628; shared_lines=172; head_lines=261; upstream_lines=185 |
|
||||
| `backend/main.py` | same path; line-set Jaccard=0.715; shared_lines=108; head_lines=138; upstream_lines=121 |
|
||||
| `backend/middleware/auth.py` | same path; line-set Jaccard=0.372; shared_lines=42; head_lines=84; upstream_lines=71 |
|
||||
| `backend/periscopex/__init__.py` | identical blob to upstream backend/__init__.py (rename/move) renamed_from=`backend/__init__.py` |
|
||||
| `backend/periscopex/bom_summary.py` | path absent; matched upstream backend/pinscopex/bom_summary.py Jaccard=0.9231; shared_lines=72 renamed_from=`backend/pinscopex/bom_summary.py` |
|
||||
| `backend/periscopex/derating.py` | path absent; matched upstream backend/pinscopex/derating.py Jaccard=0.6358; shared_lines=96 renamed_from=`backend/pinscopex/derating.py` |
|
||||
| `backend/periscopex/graph.py` | path absent; matched upstream backend/pinscopex/graph.py Jaccard=0.7812; shared_lines=275 renamed_from=`backend/pinscopex/graph.py` |
|
||||
| `backend/periscopex/led_current_check.py` | path absent; matched upstream backend/pinscopex/led_current_check.py Jaccard=0.983; shared_lines=231 renamed_from=`backend/pinscopex/led_current_check.py` |
|
||||
| `backend/periscopex/models.py` | path absent; matched upstream backend/pinscopex/models.py Jaccard=0.7241; shared_lines=252 renamed_from=`backend/pinscopex/models.py` |
|
||||
| `backend/periscopex/parsers.py` | path absent; matched upstream backend/pinscopex/parsers.py Jaccard=0.6929; shared_lines=185 renamed_from=`backend/pinscopex/parsers.py` |
|
||||
| `backend/periscopex/parsers_edif.py` | identical blob to upstream backend/pinscopex/parsers_edif.py (rename/move) renamed_from=`backend/pinscopex/parsers_edif.py` |
|
||||
| `backend/periscopex/pin_function_tokens.py` | identical blob to upstream backend/pinscopex/pin_function_tokens.py (rename/move) renamed_from=`backend/pinscopex/pin_function_tokens.py` |
|
||||
| `backend/periscopex/pin_mux_check.py` | path absent; matched upstream backend/pinscopex/pin_mux_check.py Jaccard=0.9379; shared_lines=136 renamed_from=`backend/pinscopex/pin_mux_check.py` |
|
||||
| `backend/periscopex/resolve_passives.py` | path absent; matched upstream backend/pinscopex/resolve_passives.py Jaccard=0.9652; shared_lines=388 renamed_from=`backend/pinscopex/resolve_passives.py` |
|
||||
| `backend/periscopex/taxonomy.py` | identical blob to upstream backend/pinscopex/taxonomy.py (rename/move) renamed_from=`backend/pinscopex/taxonomy.py` |
|
||||
| `backend/periscopex/utils.py` | path absent; matched upstream backend/pinscopex/utils.py Jaccard=0.8824; shared_lines=15 renamed_from=`backend/pinscopex/utils.py` |
|
||||
| `backend/periscopex/validate.py` | path absent; matched upstream backend/pinscopex/validate.py Jaccard=0.8792; shared_lines=793 renamed_from=`backend/pinscopex/validate.py` |
|
||||
| `backend/periscopex/validation_tools.py` | path absent; matched upstream backend/pinscopex/validation_tools.py Jaccard=0.7846; shared_lines=499 renamed_from=`backend/pinscopex/validation_tools.py` |
|
||||
| `backend/pipeline_worker.py` | same path; line-set Jaccard=0.817; shared_lines=94; head_lines=109; upstream_lines=100 |
|
||||
| `backend/requirements.txt` | same path; line-set Jaccard=0.696; shared_lines=16; head_lines=22; upstream_lines=17 |
|
||||
| `backend/routers/admin.py` | same path; line-set Jaccard=0.831; shared_lines=404; head_lines=429; upstream_lines=461 |
|
||||
| `backend/routers/contact.py` | same path; line-set Jaccard=0.925; shared_lines=98; head_lines=102; upstream_lines=102 |
|
||||
| `backend/routers/pipeline.py` | same path; line-set Jaccard=0.467; shared_lines=236; head_lines=490; upstream_lines=251 |
|
||||
| `backend/routers/projects.py` | same path; line-set Jaccard=0.696; shared_lines=626; head_lines=810; upstream_lines=716 |
|
||||
| `backend/routers/reports.py` | same path; line-set Jaccard=0.551; shared_lines=150; head_lines=268; upstream_lines=154 |
|
||||
| `backend/services/api_logs.py` | same path; line-set Jaccard=0.775; shared_lines=79; head_lines=100; upstream_lines=81 |
|
||||
| `backend/services/cost_estimator.py` | same path; line-set Jaccard=0.961; shared_lines=292; head_lines=298; upstream_lines=298 |
|
||||
| `backend/services/datasheet_store.py` | same path; line-set Jaccard=0.586; shared_lines=102; head_lines=168; upstream_lines=108 |
|
||||
| `backend/services/dedupe_findings.py` | same path; line-set Jaccard=0.923; shared_lines=300; head_lines=309; upstream_lines=316 |
|
||||
| `backend/services/digikey.py` | same path; line-set Jaccard=0.679; shared_lines=188; head_lines=256; upstream_lines=209 |
|
||||
| `backend/services/email.py` | same path; line-set Jaccard=0.933; shared_lines=610; head_lines=632; upstream_lines=632 |
|
||||
| `backend/services/event_bridge.py` | same path; line-set Jaccard=0.881; shared_lines=133; head_lines=145; upstream_lines=139 |
|
||||
| `backend/services/extraction.py` | same path; line-set Jaccard=0.772; shared_lines=563; head_lines=711; upstream_lines=581 |
|
||||
| `backend/services/job_runner.py` | same path; line-set Jaccard=0.744; shared_lines=203; head_lines=260; upstream_lines=216 |
|
||||
| `backend/services/llm/__init__.py` | same path; line-set Jaccard=0.838; shared_lines=31; head_lines=34; upstream_lines=34 |
|
||||
| `backend/services/llm/anthropic_provider.py` | same path; line-set Jaccard=0.911; shared_lines=276; head_lines=297; upstream_lines=282 |
|
||||
| `backend/services/llm/base.py` | same path; line-set Jaccard=0.897; shared_lines=70; head_lines=73; upstream_lines=75 |
|
||||
| `backend/services/llm/factory.py` | same path; line-set Jaccard=0.806; shared_lines=54; head_lines=62; upstream_lines=59 |
|
||||
| `backend/services/llm/gemini_provider.py` | same path; line-set Jaccard=0.958; shared_lines=276; head_lines=283; upstream_lines=281 |
|
||||
| `backend/services/llm/pricing.py` | same path; line-set Jaccard=0.742; shared_lines=66; head_lines=85; upstream_lines=70 |
|
||||
| `backend/services/llm/types.py` | same path; line-set Jaccard=0.883; shared_lines=68; head_lines=75; upstream_lines=70 |
|
||||
| `backend/services/normalize_findings.py` | same path; line-set Jaccard=0.945; shared_lines=430; head_lines=440; upstream_lines=445 |
|
||||
| `backend/services/pipeline.py` | same path; line-set Jaccard=0.657; shared_lines=1157; head_lines=1569; upstream_lines=1350 |
|
||||
| `backend/services/projects.py` | same path; line-set Jaccard=0.630; shared_lines=511; head_lines=789; upstream_lines=533 |
|
||||
| `backend/services/validation.py` | same path; line-set Jaccard=0.816; shared_lines=738; head_lines=863; upstream_lines=779 |
|
||||
| `backend/skills_manifest.json` | same path; line-set Jaccard=0.417; shared_lines=10; head_lines=17; upstream_lines=17 |
|
||||
| `frontend/CLAUDE.md` | same path; line-set Jaccard=0.882; shared_lines=75; head_lines=80; upstream_lines=80 |
|
||||
| `frontend/content/changelog.md` | same path; line-set Jaccard=0.198; shared_lines=58; head_lines=285; upstream_lines=66 |
|
||||
| `frontend/content/file-guide.md` | same path; line-set Jaccard=0.702; shared_lines=87; head_lines=107; upstream_lines=104 |
|
||||
| `frontend/content/privacy.md` | same path; line-set Jaccard=0.943; shared_lines=100; head_lines=103; upstream_lines=103 |
|
||||
| `frontend/content/terms.md` | same path; line-set Jaccard=0.936; shared_lines=176; head_lines=182; upstream_lines=182 |
|
||||
| `frontend/next.config.ts` | same path; line-set Jaccard=0.710; shared_lines=44; head_lines=59; upstream_lines=47 |
|
||||
| `frontend/package-lock.json` | same path; line-set Jaccard=0.999; shared_lines=4170; head_lines=4172; upstream_lines=4172 |
|
||||
| `frontend/package.json` | same path; line-set Jaccard=0.953; shared_lines=41; head_lines=42; upstream_lines=42 |
|
||||
| `frontend/public/favicon_io/site.webmanifest` | same path; line-set Jaccard=0.789; shared_lines=15; head_lines=17; upstream_lines=17 |
|
||||
| `frontend/src/app/(app)/admin/page.tsx` | same path; line-set Jaccard=0.983; shared_lines=1188; head_lines=1205; upstream_lines=1191 |
|
||||
| `frontend/src/app/(app)/dashboard/page.tsx` | same path; line-set Jaccard=0.980; shared_lines=300; head_lines=303; upstream_lines=303 |
|
||||
| `frontend/src/app/(app)/feedback/page.tsx` | same path; line-set Jaccard=0.981; shared_lines=102; head_lines=103; upstream_lines=103 |
|
||||
| `frontend/src/app/(app)/layout.tsx` | same path; line-set Jaccard=0.545; shared_lines=18; head_lines=27; upstream_lines=24 |
|
||||
| `frontend/src/app/(app)/project/[id]/page.tsx` | same path; line-set Jaccard=0.757; shared_lines=758; head_lines=967; upstream_lines=792 |
|
||||
| `frontend/src/app/(app)/project/[id]/progress/page.tsx` | same path; line-set Jaccard=0.729; shared_lines=266; head_lines=346; upstream_lines=285 |
|
||||
| `frontend/src/app/(app)/project/[id]/report/page.tsx` | same path; line-set Jaccard=0.721; shared_lines=271; head_lines=362; upstream_lines=285 |
|
||||
| `frontend/src/app/(marketing)/changelog/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
|
||||
| `frontend/src/app/(marketing)/contact/actions.ts` | same path; line-set Jaccard=0.949; shared_lines=37; head_lines=38; upstream_lines=38 |
|
||||
| `frontend/src/app/(marketing)/contact/nav.tsx` | same path; line-set Jaccard=0.918; shared_lines=56; head_lines=59; upstream_lines=58 |
|
||||
| `frontend/src/app/(marketing)/contact/page.tsx` | same path; line-set Jaccard=0.909; shared_lines=60; head_lines=63; upstream_lines=63 |
|
||||
| `frontend/src/app/(marketing)/file-guide/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
|
||||
| `frontend/src/app/(marketing)/landing-cta.tsx` | same path; line-set Jaccard=0.865; shared_lines=45; head_lines=49; upstream_lines=48 |
|
||||
| `frontend/src/app/(marketing)/page.tsx` | same path; line-set Jaccard=0.977; shared_lines=340; head_lines=344; upstream_lines=344 |
|
||||
| `frontend/src/app/(marketing)/privacy/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
|
||||
| `frontend/src/app/(marketing)/terms/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
|
||||
| `frontend/src/app/opengraph-image.tsx` | same path; line-set Jaccard=0.933; shared_lines=84; head_lines=87; upstream_lines=87 |
|
||||
| `frontend/src/components/dashboard/create-project-dialog.tsx` | same path; line-set Jaccard=0.849; shared_lines=1960; head_lines=2218; upstream_lines=2050 |
|
||||
| `frontend/src/components/dashboard/onboarding-survey.tsx` | same path; line-set Jaccard=0.980; shared_lines=100; head_lines=101; upstream_lines=101 |
|
||||
| `frontend/src/components/dashboard/project-card.tsx` | same path; line-set Jaccard=0.882; shared_lines=134; head_lines=147; upstream_lines=139 |
|
||||
| `frontend/src/components/dashboard/projects-table.tsx` | same path; line-set Jaccard=0.917; shared_lines=143; head_lines=153; upstream_lines=146 |
|
||||
| `frontend/src/components/layout/sidebar.tsx` | same path; line-set Jaccard=0.685; shared_lines=185; head_lines=250; upstream_lines=205 |
|
||||
| `frontend/src/components/legal/changelog-timeline.tsx` | same path; line-set Jaccard=0.974; shared_lines=151; head_lines=153; upstream_lines=153 |
|
||||
| `frontend/src/components/legal/legal-page.tsx` | same path; line-set Jaccard=0.983; shared_lines=116; head_lines=117; upstream_lines=117 |
|
||||
| `frontend/src/components/pdf/pdf-viewer-panel.tsx` | same path; line-set Jaccard=0.992; shared_lines=257; head_lines=258; upstream_lines=258 |
|
||||
| `frontend/src/components/report/component-group.tsx` | same path; line-set Jaccard=0.911; shared_lines=82; head_lines=88; upstream_lines=84 |
|
||||
| `frontend/src/components/report/finding-card.tsx` | same path; line-set Jaccard=0.696; shared_lines=167; head_lines=231; upstream_lines=176 |
|
||||
| `frontend/src/components/report/finding-focus-view.tsx` | same path; line-set Jaccard=0.935; shared_lines=115; head_lines=122; upstream_lines=116 |
|
||||
| `frontend/src/components/report/findings-list.tsx` | same path; line-set Jaccard=0.824; shared_lines=136; head_lines=161; upstream_lines=140 |
|
||||
| `frontend/src/components/report/report-filters.tsx` | same path; line-set Jaccard=0.678; shared_lines=78; head_lines=115; upstream_lines=78 |
|
||||
| `frontend/src/components/report/report-summary.tsx` | same path; line-set Jaccard=0.872; shared_lines=68; head_lines=77; upstream_lines=69 |
|
||||
| `frontend/src/components/upload/file-upload-zone.tsx` | same path; line-set Jaccard=0.364; shared_lines=55; head_lines=143; upstream_lines=63 |
|
||||
| `frontend/src/hooks/use-optional-auth.ts` | same path; line-set Jaccard=0.198; shared_lines=24; head_lines=113; upstream_lines=32 |
|
||||
| `frontend/src/hooks/use-pipeline-progress.ts` | same path; line-set Jaccard=0.953; shared_lines=202; head_lines=212; upstream_lines=202 |
|
||||
| `frontend/src/hooks/use-report.ts` | same path; line-set Jaccard=0.263; shared_lines=15; head_lines=50; upstream_lines=22 |
|
||||
| `frontend/src/hooks/use-reviewed-count.ts` | same path; line-set Jaccard=0.688; shared_lines=22; head_lines=31; upstream_lines=23 |
|
||||
| `frontend/src/hooks/use-reviewed-findings.ts` | same path; line-set Jaccard=0.841; shared_lines=58; head_lines=67; upstream_lines=60 |
|
||||
| `frontend/src/lib/api.ts` | same path; line-set Jaccard=0.726; shared_lines=605; head_lines=816; upstream_lines=622 |
|
||||
| `frontend/src/lib/csp-hosts.ts` | same path; line-set Jaccard=0.500; shared_lines=7; head_lines=13; upstream_lines=8 |
|
||||
| `frontend/src/lib/report-export.ts` | same path; line-set Jaccard=0.860; shared_lines=49; head_lines=56; upstream_lines=50 |
|
||||
| `frontend/src/lib/site.ts` | same path; line-set Jaccard=0.895; shared_lines=51; head_lines=54; upstream_lines=54 |
|
||||
| `frontend/src/lib/types.ts` | same path; line-set Jaccard=0.570; shared_lines=241; head_lines=419; upstream_lines=245 |
|
||||
| `frontend/src/lib/version.ts` | same path; line-set Jaccard=0.333; shared_lines=2; head_lines=4; upstream_lines=4 |
|
||||
| `scripts/dedup_library_datasheets.py` | same path; line-set Jaccard=0.975; shared_lines=78; head_lines=79; upstream_lines=79 |
|
||||
| `skills/extract-pintable/schema.json` | same path; line-set Jaccard=0.522; shared_lines=35; head_lines=67; upstream_lines=35 |
|
||||
| `skills/extract-pintable/validate.py` | same path; line-set Jaccard=0.416; shared_lines=57; head_lines=137; upstream_lines=57 |
|
||||
| `taxonomy/crystal.json` | same path; line-set Jaccard=0.857; shared_lines=18; head_lines=21; upstream_lines=18 |
|
||||
| `taxonomy/passive.json` | same path; line-set Jaccard=0.955; shared_lines=42; head_lines=43; upstream_lines=43 |
|
||||
| `tests/conftest.py` | same path; line-set Jaccard=0.939; shared_lines=31; head_lines=33; upstream_lines=31 |
|
||||
| `tests/impedancefinder/__init__.py` | identical blob to upstream backend/__init__.py (rename/move) renamed_from=`backend/__init__.py` |
|
||||
| `tests/test_cost_estimator.py` | same path; line-set Jaccard=0.970; shared_lines=64; head_lines=65; upstream_lines=65 |
|
||||
| `tests/test_cost_estimator_model_aware.py` | same path; line-set Jaccard=0.530; shared_lines=70; head_lines=94; upstream_lines=108 |
|
||||
| `tests/test_datasheet_excerpt_tool.py` | same path; line-set Jaccard=0.962; shared_lines=150; head_lines=153; upstream_lines=153 |
|
||||
| `tests/test_dedupe_findings.py` | same path; line-set Jaccard=0.849; shared_lines=107; head_lines=125; upstream_lines=108 |
|
||||
| `tests/test_edif_parser.py` | same path; line-set Jaccard=0.970; shared_lines=129; head_lines=131; upstream_lines=131 |
|
||||
| `tests/test_led_current_check.py` | same path; line-set Jaccard=0.957; shared_lines=90; head_lines=92; upstream_lines=92 |
|
||||
| `tests/test_netlist_parser.py` | same path; line-set Jaccard=0.974; shared_lines=76; head_lines=77; upstream_lines=77 |
|
||||
| `tests/test_normalize_findings.py` | same path; line-set Jaccard=0.869; shared_lines=153; head_lines=175; upstream_lines=154 |
|
||||
| `tests/test_pin_mux_check.py` | same path; line-set Jaccard=0.828; shared_lines=154; head_lines=183; upstream_lines=157 |
|
||||
| `tests/test_purple_parts_resolver.py` | same path; line-set Jaccard=0.961; shared_lines=368; head_lines=375; upstream_lines=376 |
|
||||
| `tests/test_validation_concurrency.py` | same path; line-set Jaccard=0.961; shared_lines=146; head_lines=149; upstream_lines=149 |
|
||||
| `tests/test_validation_deterministic_seed.py` | same path; line-set Jaccard=0.970; shared_lines=64; head_lines=65; upstream_lines=65 |
|
||||
| `tests/test_validation_no_tool_recovery.py` | same path; line-set Jaccard=0.951; shared_lines=136; head_lines=140; upstream_lines=139 |
|
||||
| `tests/test_validation_trace_log.py` | same path; line-set Jaccard=0.961; shared_lines=172; head_lines=176; upstream_lines=175 |
|
||||
| `vendor/impedancefinder/__init__.py` | identical blob to upstream backend/__init__.py (rename/move) renamed_from=`backend/__init__.py` |
|
||||
|
||||
### REPLACEMENT (4)
|
||||
|
||||
| Path | Evidence |
|
||||
| --- | --- |
|
||||
| `README.md` | same path; low overlap Jaccard=0.133; shared_lines=10; head_lines=54; upstream_lines=31 |
|
||||
| `frontend/src/components/layout/sidebar-auth.tsx` | same path; low overlap Jaccard=0.105; shared_lines=6; head_lines=55; upstream_lines=8 |
|
||||
| `frontend/src/lib/auth.ts` | same path; low overlap Jaccard=0.093; shared_lines=4; head_lines=35; upstream_lines=12 |
|
||||
| `skills/extract-pintable/SKILL.md` | same path; low overlap Jaccard=0.066; shared_lines=10; head_lines=114; upstream_lines=48 |
|
||||
|
||||
### NEW (139)
|
||||
|
||||
| Path | Evidence |
|
||||
| --- | --- |
|
||||
| `backend/periscopex/antenna_geometry.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/antenna_rf.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/bom_match_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/cad_bridge.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/crystal_cl_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/dnp_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/errata_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/eval_report.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/filter_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/finding_engine.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/functional_groups.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/hf_coverage_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/impedance.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/impedance_traces.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/internal_features_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/layout_rules.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/library_gate.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/lifecycle.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/nc_pin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/netlist_bundle.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/parsers_kicad.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/parsers_kicad_pcb.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/passive_rail_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/pcb_checks.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/pcb_inventory.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/pcb_net_match.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/pcb_power_thermal.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/pcb_review.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/pdf_text.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/placement_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/placement_pack.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/power_margin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/quote_verify.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/review_fingerprint.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/review_workflow.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/sequencing_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/si_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/periscopex/thermal_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/routers/auth.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/routers/impedance.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/datasheet_finder.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/llm/deepseek_provider.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/llm/local_skill.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/llm/pdf_ingest.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/local_jwt.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/local_users.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/mouser.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/passive_from_distributor.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/passive_from_mpn.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/passive_from_value.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/pcb_pipeline.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/pcb_validation.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/placement_pipeline.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/services/user_directory.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `backend/vendor_path.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `docker-compose.yml` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `docs/motore-finding.md` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `docs/piano-implementazione.md` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `docs/piano-pcb-review.md` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/dockerfile` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/app/(app)/library/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/app/(app)/project/[id]/pcb/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/app/(app)/project/[id]/placement/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/app/(marketing)/sign-in/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/app/(marketing)/sign-up/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/components/layout/auth-gate.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/components/project/impedance-panel.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/components/project/pcb-upload.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/components/project/topology-panel.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/components/report/finding-review-controls.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/components/report/pcb-exam-section.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/hooks/use-pcb-progress.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/hooks/use-placement-progress.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/lib/layout-finding.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `frontend/src/lib/storage-keys.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `plugins/__init__.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `plugins/kicad/__init__.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `plugins/kicad/focus.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `plugins/kicad/periscope_plugin.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `scripts/smoke_simple_project.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `scripts/update-periscope.sh` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `simple_project/eval_golden.json` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `simple_project/smoke_baseline.json` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/impedancefinder/conftest.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/impedancefinder/test_geometry.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/impedancefinder/test_net_walk.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/impedancefinder/test_planes.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/impedancefinder/test_zsolver.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_antenna_rf.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_bom_match_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_bom_parser.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_cad_bridge.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_crystal_cl_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_datasheet_finder.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_deepseek_only.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_deepseek_provider.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_derating.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_digikey_match.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_dnp_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_errata_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_eval_report.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_event_bridge.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_filter_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_finding_engine.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_finding_schema.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_functional_groups.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_hf_coverage_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_impedance.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_impedance_traces.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_internal_features_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_job_runner.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_kicad_parser.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_kicad_pcb.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_layout_rules.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_library.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_lifecycle_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_local_auth.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_nc_pin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_netlist_bundle.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_passive_from_distributor.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_passive_from_mpn.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_passive_from_value.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_passive_rail_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_pcb_review.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_pdf_ingest.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_placement_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_placement_pack.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_placement_pipeline.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_power_margin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_quote_verify.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_reopen_replace.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_reprocess.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_review_fingerprint.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_review_parse.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_review_workflow.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_roadmap_integrations.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_sequencing_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_si_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
| `tests/test_thermal_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
|
||||
|
||||
### THIRD_PARTY (9)
|
||||
|
||||
| Path | Evidence |
|
||||
| --- | --- |
|
||||
| `vendor/impedancefinder/SOURCE.md` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/geometry.py` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/model.py` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/net_analysis.py` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/net_walk.py` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/planes.py` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/report.py` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/stackup.schema.yml` | lives under vendor/; see SOURCE.md if present |
|
||||
| `vendor/impedancefinder/zsolver.py` | lives under vendor/; see SOURCE.md if present |
|
||||
|
||||
### UNKNOWN (0)
|
||||
|
||||
| Path | Evidence |
|
||||
| --- | --- |
|
||||
@@ -0,0 +1,46 @@
|
||||
# Recinto PinScope (Fase A)
|
||||
|
||||
Documented fence. **No file moves.** `LICENSE` AGPL-3.0 is unchanged. The GitHub fork `manvalan/periscope` → `Faradworks/Pinscope` is **not** detached.
|
||||
|
||||
## Branch topology
|
||||
|
||||
| Ref | Role |
|
||||
| --- | --- |
|
||||
| `origin/main` | Archival: upstream Pinscope + Docker/OSS (+3). Not the live product. |
|
||||
| `cursor/pcb-review-plan-44dd` | Live product (schema, PCB exam, finding engine). **PCB workers stay here.** |
|
||||
| `cursor/pinscope-fase-a-identity` | Fase A identity + compat fence only. Do not land engine/PCB rewrites on this branch. |
|
||||
|
||||
Merging product onto `main` is a later process step, still without detaching the fork.
|
||||
|
||||
## Inherited tree (KEEP behind the fence)
|
||||
|
||||
Treat as in-tree PinScope, not as “native Periscope”:
|
||||
|
||||
- `backend/periscopex/{graph,models,parsers*,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}`
|
||||
- `backend/services/pipeline.py`, `pipeline_worker.py`, `validation.py`, `extraction.py` (DIRECT)
|
||||
- `skills/`, `scripts/upload_skills.py`
|
||||
- Open-core Clerk/billing stubs
|
||||
- `simple_project/` fixtures, `docs/how-it-works.svg`
|
||||
|
||||
Native Periscope (do not fold into the fence): `finding_engine.py`, PCB/placement/antenna jobs, DeepSeek providers, local JWT, this identity layer.
|
||||
|
||||
## Write vs read (identifiers)
|
||||
|
||||
| Write (native) | Read-only / migrate |
|
||||
| --- | --- |
|
||||
| `periscope_token` | `pinscope_token` |
|
||||
| `periscopex:*` localStorage | `pinscopex:*` |
|
||||
| JWT `iss=periscope-local` | also accept `pinscope-local` |
|
||||
| `periscope_version` in `project.json` | also accept `pinscope_version` |
|
||||
|
||||
Code: `frontend/src/lib/pinscope-compat.ts`, `backend/pinscope_compat.py`.
|
||||
|
||||
**Do not rename** live Docker networks `pinscope_pinscope` / `pinscope_periscope` in Fase A (VPS Caddy).
|
||||
|
||||
## Identity
|
||||
|
||||
Operator of https://periscope.michelebigi.it is **Michele Bigi**. UI, TOS, privacy, metadata, and contact must not say the operator is Faradworks, Inc. Attribution to Faradworks/Pinscope remains for AGPL lineage only.
|
||||
|
||||
## Out of scope here (later phases)
|
||||
|
||||
Clerk/Anthropic runtime isolation (B). Rewrite of `validate.py` / finding engine (C). UI FindingCard (D). Parsers/graph (E). Fork detach (legal, not this plan).
|
||||
@@ -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** 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)
|
||||
@@ -115,4 +115,6 @@ Geometria board: width/thickness, via, pour courtyard, placement mm, return/stit
|
||||
|
||||
**Fase B close-out:** BOM↔PCB↔datasheet (package/pinout/V/T/I); SPOF REVIEW; EMI solo con FACT in `layout_rules`; Tj da θJA+rame+via quando tutti i parametri ci sono.
|
||||
|
||||
**Fase B leftovers:** thermal FEM / OpenEMS; ImpedenceFinder LICENSE UNKNOWN; auto-place pcbnew; PinScope identity.
|
||||
**Fase B leftovers:** thermal FEM / OpenEMS; ImpedenceFinder LICENSE UNKNOWN; auto-place pcbnew; GitHub fork detach.
|
||||
|
||||
**Fase A identity (shipped on product HEAD):** operator TOS/privacy/fence; not a `validate.py` rewrite.
|
||||
|
||||
@@ -109,10 +109,12 @@ Emmaforo USB verdict: if library has e.g. 90 Ω ±10% (81–99), Zavg ~88 Ω **P
|
||||
| CPWG / OpenEMS | Field-solver export excluded from the ImpedenceFinder vendor snapshot |
|
||||
| ImpedenceFinder license | Upstream has **no LICENSE** (UNKNOWN). Not invented in-tree. See `vendor/impedancefinder/SOURCE.md` |
|
||||
| Auto-place / pcbnew write-back | Fase C |
|
||||
| PinScope identity / TOS | Other worker |
|
||||
| Fork detach | GitHub parent Faradworks/Pinscope stays; identity Fase A does not unfork |
|
||||
|
||||
Shipped this close-out: BOM↔PCB↔datasheet (`PE-BOM-010`…`014`); SPOF REVIEW; EMI with datasheet FACT; gated θJA Tj; SI re-extract when library `layout_rules` lack SI kinds (`model_version` 1.12.0, `si_extract_needed.json`).
|
||||
Shipped PCB close-out (2.35.0): BOM↔PCB↔datasheet (`PE-BOM-010`…`014`); SPOF REVIEW; EMI with datasheet FACT; gated θJA Tj; SI re-extract when library `layout_rules` lack SI kinds (`model_version` 1.12.0, `si_extract_needed.json`). Shared `library/extracted` + one PDF exam.
|
||||
|
||||
**C — fuori:** auto-place, write-back pcbnew, unire i job, sshd/keys, PinScope identity/fork.
|
||||
Shipped identity Fase A (2.36.0): operator Michele Bigi; TOS/privacy/contact/metadata; PinScope identifier fence. `validate.py` untouched.
|
||||
|
||||
**C — fuori:** auto-place, write-back pcbnew, unire i job, sshd/keys, fork detach / PinScope engine rewrite.
|
||||
|
||||
Deploy: `ssh periscope` + `/root/periscope/scripts/update-periscope.sh`.
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
What's new in Periscope.
|
||||
|
||||
## 2.36.0 — 2026-09-20 — Fase A: operator identity + PinScope fence
|
||||
|
||||
This instance is operated by Michele Bigi. Faradworks, Inc. is not the operator. PinScope identifiers are dual-read only. PCB BOM/SPOF/EMI/θJA slices from 2.35.0 stay on this branch.
|
||||
|
||||
- [Changed] Terms, privacy, contact, footers, and HTML metadata no longer claim Faradworks, Inc.
|
||||
- [Changed] Attribution to Faradworks/Pinscope remains for AGPL lineage (`LICENSE` unchanged).
|
||||
- [Changed] `pinscope-compat` / `pinscope_compat.py`: write `periscope_*` / `periscopex:`; migrate `pinscope_*` on read.
|
||||
- [New] `docs/development/PINSCOPE_FENCE.md` — inherited tree stays in place; GitHub fork is not detached.
|
||||
|
||||
## 2.35.0 — 2026-09-20 — BOM↔PCB↔datasheet, SPOF, EMI FACT, gated Tj
|
||||
|
||||
Remaining Fase B slices except auto-place and thermal FEM / OpenEMS.
|
||||
|
||||
+35
-153
@@ -1,185 +1,67 @@
|
||||
# Periscope Privacy Policy
|
||||
|
||||
**Last updated: April 5, 2026**
|
||||
**Last updated: 20 September 2026**
|
||||
|
||||
This Privacy Policy explains how Faradworks, Inc. ("Faradworks," "we," "us," and "our") collects, uses, and discloses information in connection with the Periscope website (periscope.michelebigi.it), platform, and related services (the "Service").
|
||||
This Privacy Policy explains how Michele Bigi ("Operator," "we," "us") collects, uses, and discloses information in connection with the Periscope instance at [periscope.michelebigi.it](https://periscope.michelebigi.it) (the "Service").
|
||||
|
||||
This Privacy Policy is intended for free users and self-serve paid users. Enterprise customers typically use the Service under a separate agreement and (if applicable) a data processing agreement ("DPA"), which may include additional privacy and security terms.
|
||||
Faradworks, Inc. does **not** operate this instance and is not the controller for Personal Data processed here.
|
||||
|
||||
## 1. Key definitions
|
||||
## 1. Roles
|
||||
|
||||
**"Customer Content"** means any files, data, text, images, netlists, schematics, datasheets, design files, chat inputs, or other materials uploaded or submitted by you.
|
||||
The Operator is the controller for Personal Data used to run this instance (for example account email, authentication identifiers, and server logs).
|
||||
|
||||
**"Outputs"** means analyses, checks, summaries, recommendations, or responses generated by the Service based on Customer Content.
|
||||
If you upload Personal Data inside design files on behalf of a business, you may be the controller of that data and the Operator may process it as a processor. A DPA can be discussed at [mikbigi@gmail.com](mailto:mikbigi@gmail.com).
|
||||
|
||||
**"Derived Data"** means technical artifacts generated solely to operate the Service, such as parsed text, indexes, embeddings, summaries, or extracted metadata.
|
||||
## 2. Information we collect
|
||||
|
||||
**"Content"** refers collectively to Customer Content, Outputs, and Derived Data.
|
||||
Only what is reasonably necessary:
|
||||
|
||||
**"Personal Data"** (or "personal information") means information that identifies, relates to, describes, is reasonably capable of being associated with, or could reasonably be linked, directly or indirectly, with an individual, as defined under applicable law.
|
||||
- **Account:** name, email, password hashes / session tokens, optional organization
|
||||
- **Customer Content:** netlists, schematics, datasheets, PCB files, chat context
|
||||
- **Usage:** request logs, pipeline progress, API cost estimates for your runs
|
||||
- **Contact form:** name, email, message
|
||||
|
||||
## 2. Roles: controller vs. processor (business customers)
|
||||
If billing or Stripe is not enabled on this instance, we do not collect payment card data. We do not store card numbers.
|
||||
|
||||
Faradworks is the controller for Personal Data associated with operating the Service for users and prospective customers (for example account data, billing metadata, and website analytics).
|
||||
## 3. How we use information
|
||||
|
||||
If you upload Personal Data within Customer Content on behalf of a business, you may be the controller of that Personal Data and Faradworks may process it as a service provider/processor. Where required, we will make a DPA available upon request.
|
||||
To operate the Service: authenticate you, run reviews, store projects in instance storage, send transactional mail if configured, debug failures you report, and comply with law.
|
||||
|
||||
## 3. Information we collect
|
||||
We do **not** use Customer Content to train general-purpose or foundation AI models unless you explicitly agree.
|
||||
|
||||
We collect only the information reasonably necessary to operate the Service.
|
||||
Reviews may send excerpts of datasheets and circuit context to a configured model provider (for example DeepSeek). That provider processes the excerpt under its own terms.
|
||||
|
||||
### 3.1 Account information
|
||||
## 4. Sharing
|
||||
|
||||
- Name, email address, and organization (optional)
|
||||
- Authentication identifiers (for example password hashes, session tokens, and account IDs)
|
||||
- Subscription and plan metadata
|
||||
We share information with subprocessors only as needed to host and run the Service (compute, storage, email, AI APIs). We may disclose information if required by law or to protect the Operator and users from abuse or security incidents.
|
||||
|
||||
### 3.2 Billing information
|
||||
We do not sell Personal Data. We do not share it for cross-context behavioral advertising.
|
||||
|
||||
- Payments are processed by Stripe, a PCI-compliant third-party processor.
|
||||
- Faradworks receives billing status and subscription metadata (for example plan name, renewal date, and payment status).
|
||||
- We do not store payment card details.
|
||||
## 5. Retention
|
||||
|
||||
### 3.3 Customer Content
|
||||
Project files and accounts remain until you delete them or the Operator deletes the instance data. Residual backups may persist for a limited operational window. You may request deletion at [mikbigi@gmail.com](mailto:mikbigi@gmail.com).
|
||||
|
||||
- Engineering files (for example netlists, schematics, datasheets, and images)
|
||||
- Chat inputs and related context
|
||||
- Design rules and configuration preferences
|
||||
## 6. Security
|
||||
|
||||
### 3.4 Usage and operational data
|
||||
TLS in transit, access isolation per user when authentication is enabled, and credential hashing for local accounts. No system is perfectly secure.
|
||||
|
||||
Usage and performance telemetry (for example request timestamps, feature usage, response times, error rates, and billing/usage measurements). This data is generally operational in nature and is designed not to include the substance of your Customer Content, except as described in Section 6.3 (Support diagnostics).
|
||||
## 7. International transfers
|
||||
|
||||
### 3.5 Cookies and analytics preferences
|
||||
This instance and its subprocessors may process data in the European Union and in other regions where providers operate (including outside the EEA). Where required, appropriate safeguards will be used.
|
||||
|
||||
- We use cookies and similar technologies needed for core site operations and security.
|
||||
- We store an analytics preference cookie that records whether analytics tracking is granted or denied.
|
||||
- When analytics is granted, we may collect site and product usage and performance telemetry through providers such as Vercel Analytics and Speed Insights.
|
||||
## 8. Your rights
|
||||
|
||||
### 3.6 Contact and enterprise inquiry data
|
||||
Depending on your jurisdiction (including GDPR/UK GDPR and, where applicable, California law), you may have rights to access, correct, delete, restrict, object, and port Personal Data, and to withdraw consent.
|
||||
|
||||
- Contact and profile details submitted through contact or enterprise inquiry forms (for example name, email, business type, and capacity needs)
|
||||
- Interest signals (for example request for a demo, free review evaluation, or higher-limit self-serve access)
|
||||
- Inquiry notes and follow-up communications related to your request
|
||||
Privacy requests: [mikbigi@gmail.com](mailto:mikbigi@gmail.com) — subject line "Privacy Request".
|
||||
|
||||
## 4. How we use information
|
||||
The Service is not directed to children under 13 (or the applicable local threshold).
|
||||
|
||||
We use Personal Data and Content to:
|
||||
## 9. Changes
|
||||
|
||||
- Provide, operate, and maintain the Service
|
||||
- Perform AI-assisted engineering analysis
|
||||
- Enforce usage limits and prevent abuse
|
||||
- Diagnose errors and improve reliability
|
||||
- Respond to support requests
|
||||
- Investigate support requests and Issue Reports and, where you explicitly opt in, use those submissions and the related context to evaluate, test, debug, monitor, develop, and improve the Service
|
||||
- Send service-related communications, including onboarding, feedback requests, product updates, and security notices
|
||||
- Review and respond to contact or enterprise inquiries, including demo requests and plan-fit conversations
|
||||
- Comply with legal obligations and enforce our Terms
|
||||
Material changes will be posted on the Service or emailed before they take effect.
|
||||
|
||||
## 5. AI model training and service improvement
|
||||
## 10. Contact
|
||||
|
||||
### 5.1 No training on Customer Content for foundation models
|
||||
|
||||
Faradworks treats all Customer Content as confidential and does not use it to train, fine-tune, or improve any general-purpose or foundation AI models. Your content remains isolated to your account and is not shared across customers or included in public datasets.
|
||||
|
||||
### 5.2 Aggregated and de-identified service improvement
|
||||
|
||||
To improve and operate the Service, we rely primarily on aggregated or otherwise de-identified usage metrics (for example error rates and feature usage) that are not reasonably capable of being traced back to you.
|
||||
|
||||
## 6. How we share information
|
||||
|
||||
We do not sell Personal Data.
|
||||
|
||||
We may disclose information in the following circumstances:
|
||||
|
||||
### 6.1 Subprocessors and service providers
|
||||
|
||||
We use subprocessors and service providers to host and operate the Service (for example hosting, storage, authentication, billing, observability, and database providers). We may share Personal Data and Content with these providers only as necessary to provide the Service.
|
||||
|
||||
### 6.2 AI model providers
|
||||
|
||||
To generate Outputs, relevant portions of Customer Content are transmitted to AI model providers acting as subprocessors. Providers may include services such as DeepSeek, Anthropic, Google, or similar AI platforms.
|
||||
|
||||
These providers process Content to generate responses. Their data handling, retention, and caching practices are governed by their respective terms and our configuration.
|
||||
|
||||
### 6.3 Support and troubleshooting diagnostics
|
||||
|
||||
When needed for reliability, security, abuse prevention, or support troubleshooting, we may process account-linked technical diagnostics (for example request IDs, timestamps, stack traces, provider error payloads, and limited excerpts of inputs or outputs associated with a failing request).
|
||||
|
||||
If you submit a support request, submit an Issue Report, or otherwise ask us to investigate an issue with the Service, we may access and review the Customer Content, Outputs, and related Derived Data reasonably necessary to investigate, reproduce, resolve, remediate, and prevent recurrence of that issue. This may include the affected project, review results, uploaded files or excerpts, citations, configuration context, and associated diagnostics.
|
||||
|
||||
If, through the applicable in-product support or feedback flow, you explicitly opt in to broader improvement use, we may also use the submitted materials and the reasonably necessary related project/review context to evaluate, test, debug, monitor, support, secure, operate, develop, and improve the Service and related features, systems, and workflows, including for the benefit of other users. This may include quality evaluation, failure analysis, prompt and retrieval improvements, ranking or classification improvements, validation logic, reliability engineering, abuse prevention, and other product-quality, safety, and operational improvements.
|
||||
|
||||
We limit this access and use to authorized personnel and permitted service-related purposes. We do not use this content to train, fine-tune, or improve any general-purpose or foundation AI model unless you separately and expressly opt in to that use.
|
||||
|
||||
Outside of those circumstances, we do not access or review the substance of your Customer Content except: (a) at your request, (b) to investigate security issues or incidents, (c) if legally compelled, or (d) with your explicit consent.
|
||||
|
||||
### 6.4 Legal and safety
|
||||
|
||||
We may disclose information to comply with applicable law, lawful requests, and legal process; to protect the rights, property, and safety of Faradworks, our users, and others; and to enforce our agreements and policies.
|
||||
|
||||
### 6.5 Business transfers
|
||||
|
||||
If Faradworks is involved in a merger, acquisition, financing, reorganization, bankruptcy, or sale of assets, information may be transferred as part of that transaction, subject to standard confidentiality protections.
|
||||
|
||||
## 7. Cookies and analytics controls
|
||||
|
||||
You can control analytics cookies through the in-product or site controls (where available) and through your browser settings. If you deny analytics cookies, we will not run optional analytics tracking, but essential cookies may still be required for core functionality and security.
|
||||
|
||||
## 8. Data retention and deletion
|
||||
|
||||
### 8.1 User-controlled deletion
|
||||
|
||||
You may delete uploaded files, chats, and review history from within the Service (where available) and you may request closure of your account.
|
||||
|
||||
### 8.2 Retention periods
|
||||
|
||||
**Active accounts.** We retain Customer Content and Outputs for as long as your account remains active, unless you delete them earlier.
|
||||
|
||||
**Account closure.** When you close your account (or we close it at your request), we will delete Customer Content and Outputs associated with your account within 30 days, except where retention is required by law or for legitimate business purposes described below.
|
||||
|
||||
**Backups.** Residual metadata or encrypted backups may persist for a limited period (generally up to 90 days) for security, integrity, and disaster-recovery purposes.
|
||||
|
||||
**Operational artifacts.** We may retain limited technical artifacts (for example hashed identifiers, aggregated statistics, and anonymized error metadata) for security, abuse prevention, system integrity, and service improvement. These artifacts are not intended to and are not reasonably capable of reconstructing your original designs.
|
||||
|
||||
**Support and improvement records.** If you submit a support request or an Issue Report, we may retain the support record, related investigation notes, and the limited associated project/review context needed to document, resolve, and prevent recurrence of the issue. If you explicitly opt in to broader improvement use, we may also retain the submitted materials and related analyses for the service-improvement purposes described in this Policy, subject to the same confidentiality and deletion framework described in this Policy.
|
||||
|
||||
**Billing and accounting.** We retain billing records and aggregated usage statistics as required by law and for legitimate business purposes (for example taxes, accounting, and audit).
|
||||
|
||||
## 9. Security
|
||||
|
||||
We use commercially reasonable administrative, technical, and organizational safeguards designed to protect information, including encryption in transit (TLS) and at rest, per-user access isolation, secure credential management, and monitoring. No system is perfectly secure.
|
||||
|
||||
## 10. International data transfers
|
||||
|
||||
By default, the Service is hosted in the United States. If you access the Service from outside the United States, information may be transferred to, stored in, and processed in the United States and other countries where we or our subprocessors operate. Where required, we will use appropriate safeguards for international transfers (for example contractual protections in a DPA).
|
||||
|
||||
## 11. Your rights and choices
|
||||
|
||||
### 11.1 GDPR/UK GDPR
|
||||
|
||||
Depending on your jurisdiction, you may have rights to access, correct, delete, restrict, object to processing, and port your Personal Data. You may also have the right to withdraw consent where processing is based on consent.
|
||||
|
||||
### 11.2 California (CCPA/CPRA)
|
||||
|
||||
If you are a California resident, you may have rights to know, access, delete, correct, and opt out of the "sale" or "sharing" of Personal Data, and to limit the use of "sensitive personal information," as those terms are defined under California law. Faradworks does not sell Personal Data. We do not share Personal Data for cross-context behavioral advertising.
|
||||
|
||||
### 11.3 How to exercise rights
|
||||
|
||||
Privacy requests (data access, deletion, correction): dev@faradworks.com
|
||||
|
||||
For faster handling, use subject line: "Privacy Request (Access/Deletion/Correction)".
|
||||
|
||||
We will respond to verified privacy requests within applicable legal timelines (typically within 30 days for GDPR requests).
|
||||
|
||||
## 12. Children
|
||||
|
||||
The Service is not directed to children, and we do not knowingly collect Personal Data from children under 13 (or under the age threshold applicable in your jurisdiction).
|
||||
|
||||
## 13. Changes to this Privacy Policy
|
||||
|
||||
We may update this Privacy Policy from time to time. If we make material changes, we will provide reasonable notice by email or by posting a notice on the Service before the changes take effect. The updated policy will be effective as of the "Last updated" date unless otherwise stated.
|
||||
|
||||
## 14. Contact
|
||||
|
||||
Questions about this Privacy Policy: [dev@faradworks.com](mailto:dev@faradworks.com)
|
||||
Michele Bigi
|
||||
[mikbigi@gmail.com](mailto:mikbigi@gmail.com)
|
||||
|
||||
+52
-304
@@ -1,355 +1,103 @@
|
||||
# Periscope Terms of Service
|
||||
|
||||
**Last updated: April 5, 2026**
|
||||
**Last updated: 20 September 2026**
|
||||
|
||||
These Terms of Service ("Terms") govern access to and use of the Periscope platform (periscope.michelebigi.it) and related services (the "Service"). These Terms apply to free users and self-serve paid users. If you have a separate written agreement signed by Faradworks, Inc. (for example, an enterprise agreement), that agreement governs your use of the Service to the extent it conflicts with these Terms.
|
||||
These Terms of Service ("Terms") govern access to and use of the Periscope instance at [periscope.michelebigi.it](https://periscope.michelebigi.it) and related services operated by Michele Bigi (the "Service").
|
||||
|
||||
These Terms incorporate Faradworks' [Privacy Policy](/privacy) and any policies referenced in the Service.
|
||||
They incorporate the [Privacy Policy](/privacy).
|
||||
|
||||
By creating an account, clicking to accept these Terms (for example, by clicking an "I agree" button in our signup flow), or otherwise accessing or using the Service, you agree to be bound by these Terms. If you do not agree, do not use the Service.
|
||||
By creating an account, clicking to accept these Terms, or otherwise accessing or using the Service, you agree to be bound by these Terms. If you do not agree, do not use the Service.
|
||||
|
||||
## 1. Definitions
|
||||
## 1. Operator and software lineage
|
||||
|
||||
**"Customer Content"** means any files, data, text, images, netlists, schematics, datasheets, design files, chat inputs, or other materials uploaded or submitted by you.
|
||||
**"Operator," "we," and "us"** means Michele Bigi, operator of this Periscope instance.
|
||||
|
||||
**"Outputs"** means analyses, checks, summaries, recommendations, or responses generated by the Service based on Customer Content.
|
||||
This Service is **not** operated by Faradworks, Inc. Faradworks has no obligation, warranty, or support duty for this instance.
|
||||
|
||||
**"Derived Data"** means technical artifacts generated solely to operate the Service, such as parsed text, indexes, embeddings, summaries, or extracted metadata.
|
||||
The software is derived from the open-source [Faradworks/Pinscope](https://github.com/Faradworks/Pinscope) project and is licensed under the **GNU Affero General Public License v3.0** (see the `LICENSE` file in the source repository). That AGPL license is unchanged. Attribution to Faradworks/Pinscope does not transfer operation, trademarks, or commercial licensing of the upstream project to the Operator. For a commercial license of **upstream Pinscope**, contact Faradworks as they publish it (historically `dev@faradworks.com`).
|
||||
|
||||
**"Service"** means the Periscope platform (periscope.michelebigi.it), including all features, APIs, and related services.
|
||||
## 2. Definitions
|
||||
|
||||
**"Content"** refers collectively to Customer Content, Outputs, and Derived Data.
|
||||
**"Customer Content"** means files, data, netlists, schematics, datasheets, design files, chat inputs, or other materials you upload or submit.
|
||||
|
||||
**"Faradworks," "we," and "us"** means Faradworks, Inc., a Delaware corporation.
|
||||
**"Outputs"** means analyses, checks, summaries, recommendations, or responses generated by the Service from Customer Content.
|
||||
|
||||
## 2. Acceptance; eligibility; authority; electronic communications
|
||||
**"Derived Data"** means technical artifacts generated solely to operate the Service (parsed text, indexes, extracted metadata).
|
||||
|
||||
### 2.1 Authority.
|
||||
## 3. Informational use; no professional advice
|
||||
|
||||
If you use the Service on behalf of a company or other entity, you represent and warrant that you have authority to bind that entity. In that case, "you" and "your" refer to that entity.
|
||||
The Service analyzes submitted electrical design files and datasheets using automated systems, including machine learning models. Outputs are **informational**, provided "AS IS" and "AS AVAILABLE".
|
||||
|
||||
### 2.2 Eligibility.
|
||||
The Service does **not** provide professional engineering, safety, regulatory, certification, legal, or compliance advice. You are solely responsible for independently reviewing and validating Outputs before fabrication, procurement, or deployment.
|
||||
|
||||
You must be at least 18 years old (or the age of legal majority where you live) to use the Service.
|
||||
AI-generated Outputs are probabilistic and may be inaccurate, incomplete, or misleading.
|
||||
|
||||
### 2.3 Electronic delivery and notices.
|
||||
## 4. Accounts
|
||||
|
||||
You consent to receive communications from Faradworks electronically (for example, by email and in-product notices). You agree that all agreements, notices, disclosures, and other communications we provide electronically satisfy any legal requirement that such communications be in writing.
|
||||
You are responsible for credentials and activity under your account. Notify the Operator at [mikbigi@gmail.com](mailto:mikbigi@gmail.com) if you suspect unauthorized access.
|
||||
|
||||
## 3. Service description; informational use; no professional advice
|
||||
If the instance is configured without authentication, anyone who can reach it may have full access — do not expose that mode to the public internet.
|
||||
|
||||
### 3.1 Informational service.
|
||||
## 5. Ownership; licenses
|
||||
|
||||
The Service analyzes user-submitted electrical design files and datasheets using automated systems, including machine learning models, to generate informational Outputs. Outputs are provided on an "AS IS" and "AS AVAILABLE" basis.
|
||||
You retain all right, title, and interest in your Customer Content. To the extent permitted by law, you own Outputs generated from your Customer Content. That does not confer ownership of the Service software, models, or methods.
|
||||
|
||||
### 3.2 No professional engineering advice.
|
||||
The Operator (and upstream licensors under AGPL) retain their rights in the Service software.
|
||||
|
||||
The Service does not provide professional engineering, safety, regulatory, certification, legal, or compliance advice. You are solely responsible for independently reviewing and validating any Outputs before use in real-world designs, fabrication, procurement, manufacturing, or deployment. Outputs may be incomplete, inaccurate, or unsuitable for your specific application.
|
||||
You grant the Operator a limited, non-exclusive license to host, store, process, and transmit Customer Content and Derived Data **solely** to provide, maintain, secure, and support this instance.
|
||||
|
||||
### 3.3 AI limitations.
|
||||
The Operator will not use Customer Content to train, fine-tune, or improve any general-purpose or foundation AI model unless you separately and explicitly agree.
|
||||
|
||||
You acknowledge that AI-generated Outputs are probabilistic and may be inaccurate, incomplete, or misleading; that Output quality depends on input quality; and that models and Outputs may change over time.
|
||||
If you opt in through a support or feedback flow, submitted materials may be used to debug and improve **this** instance. You may withdraw that consent prospectively by emailing [mikbigi@gmail.com](mailto:mikbigi@gmail.com).
|
||||
|
||||
## 4. Accounts; security; administrators
|
||||
Feedback may be used without restriction or obligation.
|
||||
|
||||
### 4.1 Account security.
|
||||
## 6. Prohibited data and acceptable use
|
||||
|
||||
You are responsible for maintaining the confidentiality of your credentials and for all activity occurring under your account. You must promptly notify Faradworks if you suspect unauthorized access.
|
||||
Do not upload: protected health information; payment card data; bank secrets; government ID numbers; special-category personal data under GDPR; malware; content you lack rights to use; or material intended to disrupt the Service.
|
||||
|
||||
### 4.2 Administrators and workspace users.
|
||||
Do not circumvent security, access others' data, scrape the Service to build a competing product, or use the Service unlawfully.
|
||||
|
||||
If your account supports multiple users, administrators may be able to manage users, permissions, billing, and settings within your workspace. You are responsible for actions taken by anyone you allow to access the Service through your account.
|
||||
The Operator may suspend or terminate access for violations, abuse, legal constraints, or security reasons.
|
||||
|
||||
### 4.3 Responsibility for systems and backups.
|
||||
## 7. High-risk applications
|
||||
|
||||
You are responsible for your own systems, networks, and devices used to access the Service, and for maintaining appropriate backups of your Customer Content.
|
||||
The Service is not designed for life-critical or safety-critical systems (medical devices, automotive safety, aerospace, nuclear, weapons). If you use it in connection with such systems, you do so at your own risk.
|
||||
|
||||
## 5. Ownership; licenses; feedback
|
||||
## 8. Third parties
|
||||
|
||||
### 5.1 Your ownership.
|
||||
The Service may call third-party providers (including AI model APIs, hosting, and storage). Their terms apply to their services. The Operator is not responsible for third-party outages.
|
||||
|
||||
You retain all right, title, and interest in and to your Customer Content.
|
||||
## 9. Confidentiality and security
|
||||
|
||||
### 5.2 Outputs.
|
||||
Customer Content is treated as confidential and is not disclosed except to operate the Service (including subprocessors), as required by law, or with your consent.
|
||||
|
||||
To the extent permitted by law, you own the Outputs generated from your Customer Content. Ownership of Outputs does not confer ownership of the Service, software, models, or analytical methods used to generate them.
|
||||
No system is perfectly secure. The Operator does not guarantee that unauthorized access or data loss will never occur.
|
||||
|
||||
### 5.3 Faradworks ownership.
|
||||
If you file a support request, the Operator may access the related project and diagnostics to investigate.
|
||||
|
||||
Faradworks retains all right, title, and interest in and to the Service (including its software, models, workflows, analytical methods, user interfaces, and underlying infrastructure), including all improvements and derivatives.
|
||||
## 10. Disclaimers and liability
|
||||
|
||||
### 5.4 License to operate the Service.
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SERVICE AND OUTPUTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
|
||||
|
||||
You grant Faradworks a limited, non-exclusive, worldwide, royalty-free license to host, store, process, transmit, reproduce (as necessary for processing), and otherwise use your Customer Content and Derived Data solely to provide, maintain, secure, and support the Service. Any broader use of Customer Content, Outputs, support requests, Issue Reports, or related project/review context for service-improvement purposes is permitted only to the extent expressly described in these Terms, the Privacy Policy, and any opt-in choices you make in the Service.
|
||||
TO THE FULLEST EXTENT PERMITTED BY LAW, THE OPERATOR IS NOT LIABLE FOR INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR FOR HARDWARE FAILURES OR SAFETY INCIDENTS RELATED TO DESIGNS REVIEWED WITH THE SERVICE.
|
||||
|
||||
### 5.5 No model training on Customer Content.
|
||||
THE OPERATOR'S TOTAL LIABILITY FOR ALL CLAIMS RELATING TO THE SERVICE WILL NOT EXCEED THE FEES YOU PAID THE OPERATOR FOR THE SERVICE IN THE TWELVE MONTHS BEFORE THE CLAIM, OR ONE HUNDRED EUROS (€100) IF YOU PAID NO FEES.
|
||||
|
||||
Faradworks will not use Customer Content to train, fine-tune, or improve any general-purpose or foundation AI model, and will not permit third parties to do so, unless you separately and explicitly agree to that use.
|
||||
Nothing excludes liability that cannot be limited under applicable law (including mandatory Italian consumer protections, where they apply).
|
||||
|
||||
### 5.6 Support reports and optional improvement consent.
|
||||
## 11. Governing law
|
||||
|
||||
By default, Faradworks will not use Customer Content, Outputs, support requests, Issue Reports, or related project/review context to improve the Service beyond investigating, resolving, and preventing recurrence of the specific incident for which such materials were submitted.
|
||||
These Terms are governed by the laws of Italy, without regard to conflict-of-law rules. Courts of Italy have jurisdiction, subject to mandatory consumer venue rules.
|
||||
|
||||
If you explicitly opt in through the applicable in-product support or feedback flow, you grant Faradworks a limited, revocable, non-exclusive, worldwide, royalty-free license to access, review, retain, and use the submitted materials and the reasonably necessary related Customer Content, Outputs, Derived Data, and project/review context to evaluate, test, debug, monitor, support, secure, operate, develop, and improve the Service and related features, systems, and workflows. This may include quality evaluation, failure analysis, prompt and retrieval improvements, ranking or classification improvements, validation logic, reliability engineering, abuse prevention, and other product-quality, safety, and operational improvements for the benefit of current and future users.
|
||||
Before filing a claim (other than for injunctive relief), contact [mikbigi@gmail.com](mailto:mikbigi@gmail.com) and allow 30 days for an informal resolution attempt.
|
||||
|
||||
This consent does not authorize Faradworks or any third party to train, fine-tune, or improve any general-purpose or foundation AI model unless you separately and expressly agree to that use.
|
||||
## 12. Changes
|
||||
|
||||
You may withdraw this optional improvement consent at any time through the Service settings or by contacting Faradworks. Withdrawal will apply prospectively and will not require Faradworks to unwind or delete improvements, evaluations, or analyses already created or completed before withdrawal, subject to the Privacy Policy's retention and deletion terms.
|
||||
Material changes will be announced in the Service or by email. Continued use after the effective date constitutes acceptance. If you do not agree, stop using the Service.
|
||||
|
||||
### 5.7 Feedback.
|
||||
## 13. Contact
|
||||
|
||||
If you provide suggestions or feedback, you grant Faradworks a perpetual, irrevocable, worldwide, royalty-free license to use and incorporate it without restriction or obligation.
|
||||
|
||||
## 6. Customer responsibilities; prohibited data; export and restricted data
|
||||
|
||||
### 6.1 Rights in content you upload.
|
||||
|
||||
You represent and warrant that you have all rights necessary to upload and use Customer Content with the Service and to grant the rights in these Terms.
|
||||
|
||||
### 6.2 Prohibited Data.
|
||||
|
||||
Unless Faradworks expressly agrees in writing, you will not submit or upload any of the following ("Prohibited Data"): (a) patient, medical, or other protected health information regulated by HIPAA or similar laws; (b) payment card data subject to PCI DSS; (c) bank account numbers, passwords, or authentication secrets intended to access financial accounts; (d) social security numbers, driver's license numbers, or other unique government ID numbers; (e) "special categories" of personal data under the GDPR (or similar sensitive personal data classifications); or (f) any other similarly sensitive personal information that would impose heightened legal or regulatory obligations on Faradworks.
|
||||
|
||||
### 6.3 GDPR / DPA.
|
||||
|
||||
If you are subject to GDPR/UK GDPR and need a data processing agreement ("DPA"), contact us at [dev@faradworks.com](mailto:dev@faradworks.com). If we provide a DPA for your use case, you must execute it before submitting personal data governed by GDPR/UK GDPR as Customer Content. If a DPA applies, it will govern the parties' rights and obligations with respect to such personal data and will control in the event of conflict with these Terms.
|
||||
|
||||
### 6.4 Export-controlled and restricted technical data.
|
||||
|
||||
You are responsible for compliance with all applicable export control laws and regulations, including U.S. EAR and ITAR. You agree not to upload export-controlled technical data, classified information, or government-restricted information without proper authorization. By default, the Service is hosted in the United States. By using the Service, you acknowledge that Content may be processed and stored in the U.S. or other regions where we or our subprocessors operate.
|
||||
|
||||
## 7. Acceptable use; restrictions
|
||||
|
||||
You agree not to:
|
||||
|
||||
- (a) upload content you do not have rights to use;
|
||||
- (b) upload malware, malicious code, or content intended to disrupt or compromise the Service;
|
||||
- (c) circumvent usage limits or security controls;
|
||||
- (d) interfere with service integrity or access others' data;
|
||||
- (e) systematically extract, scrape, benchmark, or reverse engineer the Service, Outputs, or underlying models for the purpose of building a competing product;
|
||||
- (f) resell, rent, or redistribute the Service without Faradworks' written consent; or
|
||||
- (g) use the Service for illegal purposes or in violation of applicable laws.
|
||||
|
||||
Faradworks may suspend or terminate access for violations of this section.
|
||||
|
||||
## 8. High-risk applications
|
||||
|
||||
The Service is not designed or intended for use in life-critical or safety-critical systems where failure could result in death, bodily injury, or significant property or environmental damage, including medical devices, automotive safety systems, aerospace systems, nuclear facilities, or weapons systems.
|
||||
|
||||
If you choose to use the Service in connection with such applications, you do so at your own risk. You are solely responsible for independently validating all Outputs and for ensuring that any resulting designs, products, or systems meet all applicable safety, regulatory, and certification requirements.
|
||||
|
||||
## 9. Free trials; beta features; no reliance
|
||||
|
||||
### 9.1 Free and trial access.
|
||||
|
||||
Faradworks may offer free plans, free trials, promotional credits, or other no-fee access ("Free Access"). Faradworks may modify, suspend, or end Free Access at any time.
|
||||
|
||||
### 9.2 Beta features.
|
||||
|
||||
Faradworks may make available features labeled alpha, beta, preview, or similar ("Beta Features"). Beta Features are experimental and may be changed or discontinued at any time.
|
||||
|
||||
### 9.3 No SLA; no reliance.
|
||||
|
||||
Free Access and Beta Features are provided "AS IS" and "AS AVAILABLE," without any service level commitment and without any obligation to provide support. You should not rely on Free Access or Beta Features for production use.
|
||||
|
||||
## 10. Third-party providers; subprocessors; third-party services
|
||||
|
||||
### 10.1 Subprocessors.
|
||||
|
||||
The Service relies on third-party providers (including AI model providers, hosting, storage, authentication, billing, and observability providers) to help deliver functionality. Faradworks' data handling practices are described in the Privacy Policy.
|
||||
|
||||
### 10.2 Third-party services/links.
|
||||
|
||||
The Service may integrate with or link to third-party services. Faradworks is not responsible for third-party services, and your use of them is governed by the third party's terms and policies.
|
||||
|
||||
## 11. Usage limits; billing; usage allocations; taxes; refunds
|
||||
|
||||
### 11.1 Usage limits.
|
||||
|
||||
Plans may include limits on reviews, tokens, files, API spend, usage allocations, or other usage metrics. Faradworks may throttle, restrict, or suspend usage to enforce limits or protect system stability.
|
||||
|
||||
### 11.2 Prepaid service credits.
|
||||
|
||||
Certain Services, plans, or features may allow or require you to prepay for future eligible Periscope review services for professional, business, or organizational use by purchasing prepaid service credits ("Usage Credits"). Usage Credits represent a prepaid, limited, revocable, non-transferable license to access eligible Periscope review services up to the applicable credited amount and may be used only for eligible Periscope review charges as described in the Service. Faradworks may also, in its sole discretion, provide free or promotional credits ("Promotional Credits"), which may be subject to additional restrictions or expiration dates stated when issued.
|
||||
|
||||
### 11.3 Credit characteristics and workspace scope.
|
||||
|
||||
Credits may be used only for eligible Periscope review charges and may not be used for any other product or service unless Faradworks expressly states otherwise in the Service. Credits are not legal tender, are not currency, are not redeemable for cash, are not refundable except as required by law or expressly stated by Faradworks, do not constitute or confer any personal property right, and do not constitute a bank account, deposit account, stored-value account, digital wallet, payment instrument, or other monetary account. Credits are an internal service accounting mechanism that measures the amount of eligible Periscope review services you have prepaid and are licensed to use. Any credit balance or similar amount displayed in the Service reflects only our record of remaining prepaid eligibility for future eligible review charges and does not represent money held on your behalf. Credits are non-transferable, may not be sold, assigned, gifted, or sublicensed, and may be used only by the workspace or account to which they are issued. If credits are issued to an organization or workspace, they belong to that workspace and may be consumed by authorized users acting within that workspace.
|
||||
|
||||
### 11.4 Credit purchases and application to charges.
|
||||
|
||||
Your order for Usage Credits constitutes an offer to purchase those Usage Credits. Faradworks may accept or reject any purchase request in its discretion. Credits are issued when Faradworks confirms the purchase or otherwise makes the credits available in your account or workspace. Credits are applied to eligible Periscope review charges in the manner described in the Service. Faradworks may reserve, deduct, reverse, release, or adjust credits to reflect quoted charges, completed usage, failed runs, duplicate requests, fraud checks, refunds, chargebacks, or billing corrections. Credit pricing, minimum purchase amounts, maximum purchase amounts, and applicable taxes will be shown in the Service or at checkout. Fees are exclusive of taxes unless stated otherwise.
|
||||
|
||||
### 11.5 Credit expiration, forfeiture, and promotional credits.
|
||||
|
||||
Unless otherwise stated in the Service or required by law, purchased Usage Credits expire 12 months after the date they are issued to your account or workspace. Expiration does not restart because credits are partially used, because you change plans, or because your workspace or account settings change, unless Faradworks expressly states otherwise. Promotional Credits expire on the date stated when they are issued, or if no date is stated, 12 months after issuance unless required otherwise by law. Credits may be forfeited if the applicable account or workspace is closed, terminated, or suspended, subject to applicable law. Faradworks may also withhold, void, or reverse credits associated with chargebacks, payment reversals, fraud, abuse, or violations of these Terms.
|
||||
|
||||
### 11.6 Per-review usage impact.
|
||||
|
||||
Paid plans may include monthly usage limits, included usage budgets, or similar usage allocations. These allocations reset each billing period unless otherwise stated. Each review has a usage impact based on scope, selected model, and your applicable plan or pricing structure. Before you run a review, Faradworks will show the usage impact of that review, which may be displayed as a dollar amount, credit amount, percentage of plan limit, or similar usage metric. That usage impact may change over time, including for the same or a similar review configuration, and prepaid credits do not lock in a future review price unless Faradworks expressly states otherwise in the Service. Unless Faradworks expressly states otherwise, the applicable review charge is the quoted amount or usage impact shown in the Service at the time you submit or start the review, and any quote may expire or require refresh before use.
|
||||
|
||||
### 11.7 Plan changes.
|
||||
|
||||
If you change plans mid-cycle, Faradworks may apply prorated or other adjustments to your available usage limits, credits, budget, or similar usage allocation for that active billing period.
|
||||
|
||||
### 11.8 Auto-renewal; cancellation.
|
||||
|
||||
Paid subscriptions renew automatically unless canceled. You may cancel at any time through your account settings. Cancellation stops future renewals; it does not retroactively refund fees already paid except where required by law.
|
||||
|
||||
### 11.9 Grandfathered plans.
|
||||
|
||||
Grandfathered plans may remain available only while the subscription stays active and in good standing. If a grandfathered plan is canceled, lapses, or is changed, reactivation may require enrolling in a then-current plan. Faradworks may retire grandfathered plans with reasonable notice where permitted by law.
|
||||
|
||||
### 11.10 Taxes.
|
||||
|
||||
Fees are exclusive of taxes unless stated otherwise. You are responsible for applicable taxes, except taxes based on Faradworks' net income.
|
||||
|
||||
### 11.11 Refunds and billing corrections.
|
||||
|
||||
You may request a refund for completely unused Usage Credits within 24 hours after purchase by contacting [dev@faradworks.com](mailto:dev@faradworks.com). If no refund request is received within 24 hours after purchase, unused Usage Credits become non-refundable except where required by law. Once any portion of purchased Usage Credits has been used, that purchase becomes non-refundable except where required by law or expressly authorized by Faradworks. Otherwise, refunds are provided at Faradworks' discretion or as required by law. Faradworks may correct pricing errors, mistaken issuances, duplicate grants, or accounting mistakes, including by adjusting, removing, or restoring credits where appropriate.
|
||||
|
||||
## 12. Confidentiality; security; support access
|
||||
|
||||
### 12.1 Confidential Customer Content.
|
||||
|
||||
Faradworks treats Customer Content as confidential and does not disclose it to third parties except as necessary to provide the Service (including to subprocessors), as required by law, or with your consent, as further described in the Privacy Policy.
|
||||
|
||||
### 12.2 Security.
|
||||
|
||||
Faradworks uses commercially reasonable administrative, technical, and organizational safeguards designed to protect Content. However, no system is perfectly secure and Faradworks does not guarantee that unauthorized access, hacking, data loss, or other security incidents will never occur.
|
||||
|
||||
### 12.3 Security incident communications.
|
||||
|
||||
Where required by applicable law, Faradworks will provide notice of a confirmed unauthorized access to personal data under our control.
|
||||
|
||||
### 12.4 Your responsibility for sensitive material.
|
||||
|
||||
You are responsible for evaluating whether the Service meets your confidentiality requirements before uploading sensitive or proprietary designs.
|
||||
|
||||
### 12.5 Support troubleshooting access.
|
||||
|
||||
If you submit a support request or an Issue Report, Faradworks may access account-linked diagnostic records and the Customer Content, Outputs, and related project/review context reasonably necessary to investigate, reproduce, resolve, remediate, and prevent recurrence of the reported issue. This may include the affected project, review results, uploaded files or excerpts, citations, and associated diagnostics.
|
||||
|
||||
If you explicitly opt in through the applicable support or feedback flow, Faradworks may also use those submitted materials and related context for the broader service-improvement purposes described in Section 5.6. Faradworks will limit such access and use to authorized personnel and to the permitted purposes described in these Terms and the Privacy Policy.
|
||||
|
||||
## 13. Suspension; termination; effect of termination
|
||||
|
||||
### 13.1 Suspension/termination by Faradworks.
|
||||
|
||||
Faradworks may suspend or terminate access for: (a) violations of these Terms; (b) excessive, abusive, or fraudulent usage; (c) non-payment; (d) legal, regulatory, or infrastructure constraints; or (e) security, abuse-prevention, or risk-management reasons.
|
||||
|
||||
### 13.2 Termination by you.
|
||||
|
||||
You may stop using the Service at any time and may request account closure.
|
||||
|
||||
### 13.3 Effect.
|
||||
|
||||
Upon termination, your right to access the Service ends immediately. Faradworks will delete your Content in accordance with the Privacy Policy's data retention and deletion terms, subject to legal retention requirements and limited residual technical artifacts described in the Privacy Policy.
|
||||
|
||||
### 13.4 Survival.
|
||||
|
||||
Sections that by their nature should survive will survive termination, including ownership, confidentiality, disclaimers, limitation of liability, indemnification, disputes, and general terms.
|
||||
|
||||
## 14. DISCLAIMERS
|
||||
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SERVICE AND OUTPUTS ARE PROVIDED "AS IS" AND "AS AVAILABLE," WITHOUT WARRANTIES OF ANY KIND, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ACCURACY, AND QUIET ENJOYMENT.
|
||||
|
||||
FARADWORKS DOES NOT WARRANT THAT THE SERVICE WILL BE UNINTERRUPTED, ERROR-FREE, SECURE, OR FREE OF HARMFUL COMPONENTS, OR THAT OUTPUTS WILL MEET YOUR REQUIREMENTS OR BE CORRECT FOR ANY PARTICULAR USE.
|
||||
|
||||
## 15. LIMITATION OF LIABILITY
|
||||
|
||||
### 15.1 EXCLUSION OF CERTAIN DAMAGES.
|
||||
|
||||
TO THE FULLEST EXTENT PERMITTED BY LAW, IN NO EVENT WILL FARADWORKS (OR ITS AFFILIATES, OFFICERS, DIRECTORS, EMPLOYEES, CONTRACTORS, AGENTS, LICENSORS, SUPPLIERS, OR SUBPROCESSORS) BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF PROFITS, REVENUE, BUSINESS OPPORTUNITIES, GOODWILL, OR DATA, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
### 15.2 SPECIFIC EXCLUSIONS.
|
||||
|
||||
TO THE FULLEST EXTENT PERMITTED BY LAW, FARADWORKS IS NOT LIABLE FOR: (a) ERROR OR INTERRUPTION OF USE; (b) LOSS, INACCURACY, CORRUPTION, OR UNAUTHORIZED DISCLOSURE OF DATA OR CONTENT; (c) COST OF PROCUREMENT OF SUBSTITUTE GOODS, SERVICES, OR TECHNOLOGY; (d) HARDWARE FAILURES, DESIGN DEFECTS, MANUFACTURING ISSUES, OR SAFETY INCIDENTS RELATED TO DESIGNS REVIEWED USING THE SERVICE; OR (e) ANY MATTER BEYOND FARADWORKS' REASONABLE CONTROL (INCLUDING THIRD-PARTY PROVIDER FAILURES).
|
||||
|
||||
### 15.3 AGGREGATE CAP.
|
||||
|
||||
TO THE FULLEST EXTENT PERMITTED BY LAW, FARADWORKS' TOTAL AGGREGATE AND CUMULATIVE LIABILITY FOR ALL CLAIMS ARISING OUT OF OR RELATING TO THESE TERMS OR THE SERVICE, UNDER ANY THEORY OF LIABILITY (CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, STATUTE, OR OTHERWISE), WILL NOT EXCEED THE FEES PAID BY YOU TO FARADWORKS FOR THE SERVICE IN THE TWELVE (12) MONTHS PRECEDING THE FIRST EVENT GIVING RISE TO THE CLAIM. IF YOU HAVE NOT PAID ANY FEES TO FARADWORKS IN THAT PERIOD (FOR EXAMPLE, DURING FREE ACCESS OR A FREE TRIAL), FARADWORKS' TOTAL LIABILITY WILL NOT EXCEED ONE HUNDRED U.S. DOLLARS (US$100).
|
||||
|
||||
### 15.4 BASIS OF THE BARGAIN; FAILURE OF ESSENTIAL PURPOSE.
|
||||
|
||||
YOU ACKNOWLEDGE THAT THE FEES (IF ANY) REFLECT THE ALLOCATION OF RISK AND THAT FARADWORKS WOULD NOT PROVIDE THE SERVICE WITHOUT THESE LIMITATIONS. THE LIMITATIONS IN THIS SECTION APPLY EVEN IF ANY LIMITED REMEDY FAILS OF ITS ESSENTIAL PURPOSE.
|
||||
|
||||
### 15.5 LIMITATIONS REQUIRED BY LAW.
|
||||
|
||||
NOTHING IN THESE TERMS LIMITS OR EXCLUDES LIABILITY TO THE EXTENT SUCH LIMITATION OR EXCLUSION IS PROHIBITED BY APPLICABLE LAW.
|
||||
|
||||
## 16. Indemnification
|
||||
|
||||
### 16.1 Your indemnity.
|
||||
|
||||
You agree to indemnify, defend, and hold harmless Faradworks and its affiliates, officers, directors, employees, contractors, and agents from and against any claims, damages, losses, liabilities, and expenses (including reasonable attorneys' fees) arising out of or relating to: (a) your use of the Service or Outputs; (b) your Customer Content; (c) your violation of these Terms; (d) your violation of any third-party rights (including IP rights); or (e) designs, products, or systems you create using or informed by Outputs.
|
||||
|
||||
### 16.2 Indemnification procedure.
|
||||
|
||||
Your obligations under this Section 16 are conditioned on Faradworks: (a) providing you prompt notice of the claim (provided that failure to provide prompt notice will relieve you only to the extent materially prejudiced); (b) providing reasonable assistance at your expense; and (c) allowing you sole control of the defense and settlement of the claim, except that you may not settle any claim in a manner that admits fault by Faradworks or imposes obligations on Faradworks without Faradworks' prior written consent. Faradworks may participate in the defense with counsel of its choosing at its own expense.
|
||||
|
||||
## 17. Force majeure
|
||||
|
||||
Faradworks will not be liable for any delay or failure to perform due to events beyond its reasonable control, including acts of God, natural disasters, war, terrorism, riots, labor disputes, pandemics, public utility failures, internet or cloud provider failures, or governmental actions ("Force Majeure Events"). A Force Majeure Event does not excuse your payment obligations for fees accrued prior to the Force Majeure Event.
|
||||
|
||||
## 18. Disputes; governing law; venue; time limit to bring claims
|
||||
|
||||
### 18.1 Informal resolution.
|
||||
|
||||
Before filing a claim (other than for injunctive relief), you agree to first contact Faradworks at [dev@faradworks.com](mailto:dev@faradworks.com) with a brief description of the dispute and your contact information. The parties will attempt in good faith to resolve the dispute for at least 30 days.
|
||||
|
||||
### 18.2 Governing law.
|
||||
|
||||
These Terms are governed by the laws of the State of California, without regard to conflict of law principles.
|
||||
|
||||
### 18.3 Venue.
|
||||
|
||||
Any disputes arising out of or relating to these Terms or the Service must be brought in the state or federal courts located in California, and each party consents to personal jurisdiction and venue there.
|
||||
|
||||
### 18.4 Injunctive relief.
|
||||
|
||||
Nothing in these Terms prevents either party from seeking injunctive or other equitable relief to protect its intellectual property or confidential information.
|
||||
|
||||
### 18.5 Time limit to bring claims.
|
||||
|
||||
To the fullest extent permitted by law, any claim arising out of or relating to these Terms or the Service must be filed within one (1) year after the cause of action accrues; otherwise, it is permanently barred.
|
||||
|
||||
## 19. Changes to Terms
|
||||
|
||||
We may update these Terms from time to time. If we make material changes, we will provide reasonable notice by email or by posting a notice on the Service before the changes take effect. The updated Terms will be effective as of the "Last updated" date unless otherwise stated. Your continued use of the Service after the effective date, including accepting the updated Terms through the Service, constitutes acceptance of the updated Terms. If you do not agree to the changes, you must stop using the Service and close your account.
|
||||
|
||||
## 20. General terms
|
||||
|
||||
### 20.1 Entire agreement; order of precedence.
|
||||
|
||||
These Terms (including the [Privacy Policy](/privacy) and any policies referenced in the Service) are the entire agreement between you and Faradworks regarding the Service and supersede prior or contemporaneous agreements or understandings. If you have a separate written agreement signed by Faradworks that expressly governs the Service, that agreement will control to the extent of conflict.
|
||||
|
||||
### 20.2 Severability.
|
||||
|
||||
If any provision of these Terms is held invalid or unenforceable, the remaining provisions will remain in full force and effect.
|
||||
|
||||
### 20.3 Waiver.
|
||||
|
||||
Failure to enforce any provision is not a waiver of future enforcement.
|
||||
|
||||
### 20.4 Assignment.
|
||||
|
||||
You may not assign these Terms without Faradworks' prior written consent. Faradworks may assign these Terms in connection with a merger, acquisition, corporate reorganization, or sale of all or substantially all of its assets, or otherwise upon notice.
|
||||
|
||||
### 20.5 No third-party beneficiaries.
|
||||
|
||||
Except for Faradworks' affiliates, licensors, suppliers, and subprocessors as intended third-party beneficiaries of Sections 14 (Disclaimers) and 15 (Limitation of Liability), there are no third-party beneficiaries to these Terms.
|
||||
|
||||
### 20.6 Independent contractors.
|
||||
|
||||
The parties are independent contractors. Nothing in these Terms creates any agency, partnership, joint venture, or employment relationship.
|
||||
|
||||
### 20.7 Headings.
|
||||
|
||||
Headings are for convenience only and do not affect interpretation.
|
||||
|
||||
## 21. Contact
|
||||
|
||||
Faradworks, Inc.
|
||||
|
||||
Questions about these Terms or the Privacy Policy: [dev@faradworks.com](mailto:dev@faradworks.com)
|
||||
Michele Bigi — Periscope operator
|
||||
[mikbigi@gmail.com](mailto:mikbigi@gmail.com)
|
||||
[https://periscope.michelebigi.it](https://periscope.michelebigi.it)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CONTACT_EMAIL } from "@/lib/site";
|
||||
|
||||
const BASE = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:18741";
|
||||
|
||||
export type ActionState = {
|
||||
@@ -29,7 +31,7 @@ export async function submitContactForm(data: {
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
message: result.message || "Something went wrong. Please try again or email us directly at dev@faradworks.com.",
|
||||
message: result.message || `Something went wrong. Please try again or email us directly at ${CONTACT_EMAIL}.`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +39,7 @@ export async function submitContactForm(data: {
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
message: "Could not reach the server. Please try again or email us directly at dev@faradworks.com.",
|
||||
message: `Could not reach the server. Please try again or email us directly at ${CONTACT_EMAIL}.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import Link from "next/link";
|
||||
import { Cpu, ArrowRight } from "lucide-react";
|
||||
import { ContactForm } from "./contact-form";
|
||||
import { Nav } from "./nav";
|
||||
import { pageMetadata } from "@/lib/site";
|
||||
import { CONTACT_EMAIL, OPERATOR_NAME, pageMetadata } from "@/lib/site";
|
||||
|
||||
export const metadata = pageMetadata({
|
||||
title: "Contact",
|
||||
@@ -40,10 +40,10 @@ export default function ContactPage() {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Prefer email? Reach us directly at{" "}
|
||||
<a
|
||||
href="mailto:dev@faradworks.com"
|
||||
href={`mailto:${CONTACT_EMAIL}`}
|
||||
className="text-foreground hover:underline"
|
||||
>
|
||||
dev@faradworks.com
|
||||
{CONTACT_EMAIL}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ export default function ContactPage() {
|
||||
>
|
||||
Terms
|
||||
</Link>
|
||||
<span>© {new Date().getFullYear()} Faradworks</span>
|
||||
<span>© {new Date().getFullYear()} {OPERATOR_NAME}</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Cpu, Shield, Lock, ServerCog, Users, GitBranch } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ThemeToggle } from "@/components/theme/theme-toggle";
|
||||
import { APP_VERSION_DATE } from "@/lib/version";
|
||||
import { SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/lib/site";
|
||||
import { SITE_DESCRIPTION, SITE_NAME, SITE_URL, CONTACT_EMAIL, OPERATOR_NAME } from "@/lib/site";
|
||||
import {
|
||||
PRICING_JSON_LD_OFFERS,
|
||||
PRICING_NAV_LINK,
|
||||
@@ -74,32 +74,20 @@ const jsonLd = [
|
||||
screenshot: `${SITE_URL}/report.png`,
|
||||
...(PRICING_JSON_LD_OFFERS ? { offers: PRICING_JSON_LD_OFFERS } : {}),
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Faradworks",
|
||||
url: "https://faradworks.com",
|
||||
"@type": "Person",
|
||||
name: OPERATOR_NAME,
|
||||
url: SITE_URL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
name: "Faradworks",
|
||||
url: "https://faradworks.com",
|
||||
logo: `${SITE_URL}/faradworks-logo-white.png`,
|
||||
sameAs: [
|
||||
"https://www.linkedin.com/company/faradworks",
|
||||
"https://x.com/getFaradWorks",
|
||||
],
|
||||
address: {
|
||||
"@type": "PostalAddress",
|
||||
streetAddress: "33 W 17th St",
|
||||
addressLocality: "New York",
|
||||
addressRegion: "NY",
|
||||
addressCountry: "US",
|
||||
},
|
||||
name: SITE_NAME,
|
||||
url: SITE_URL,
|
||||
contactPoint: {
|
||||
"@type": "ContactPoint",
|
||||
contactType: "customer support",
|
||||
email: "dev@faradworks.com",
|
||||
email: CONTACT_EMAIL,
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -390,10 +378,10 @@ export default function LandingPage() {
|
||||
<p className="mt-4 text-xs text-muted-foreground">
|
||||
Questions? Reach us at{" "}
|
||||
<a
|
||||
href="mailto:dev@faradworks.com"
|
||||
href={`mailto:${CONTACT_EMAIL}`}
|
||||
className="text-foreground hover:underline"
|
||||
>
|
||||
dev@faradworks.com
|
||||
{CONTACT_EMAIL}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -404,43 +392,23 @@ export default function LandingPage() {
|
||||
<div className="mx-auto max-w-6xl px-6 py-10">
|
||||
<div className="flex flex-col gap-8 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Image
|
||||
src="/faradworks-logo-white.png"
|
||||
alt="Faradworks"
|
||||
width={569}
|
||||
height={230}
|
||||
className="h-8 w-auto self-start opacity-90 invert dark:invert-0"
|
||||
/>
|
||||
<address className="text-xs not-italic leading-relaxed text-muted-foreground">
|
||||
33 W 17th St
|
||||
<br />
|
||||
New York, NY
|
||||
</address>
|
||||
<div className="flex items-center gap-2 text-sm text-foreground">
|
||||
<Cpu className="h-4 w-4 text-blue-500" />
|
||||
<span>{SITE_NAME}</span>
|
||||
</div>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground max-w-sm">
|
||||
Operated by {OPERATOR_NAME}. Software derived from
|
||||
Faradworks/Pinscope (AGPL-3.0).
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-4 sm:items-end">
|
||||
<a
|
||||
href="https://www.linkedin.com/company/faradworks"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Faradworks on LinkedIn"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="h-4 w-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.86 0-2.14 1.45-2.14 2.95v5.66H9.34V9h3.42v1.56h.05a3.75 3.75 0 0 1 3.37-1.85c3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.07 2.07 0 1 1 0-4.13 2.07 2.07 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.23 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.46c.98 0 1.77-.77 1.77-1.72V1.72C24 .77 23.21 0 22.23 0z" />
|
||||
</svg>
|
||||
</a>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<Link href="/contact" className="hover:text-foreground transition-colors">Contact</Link>
|
||||
<Link href="/changelog" className="hover:text-foreground transition-colors">Changelog</Link>
|
||||
<Link href="/privacy" className="hover:text-foreground transition-colors">Privacy</Link>
|
||||
<Link href="/terms" className="hover:text-foreground transition-colors">Terms</Link>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">© {new Date().getFullYear()} Faradworks</span>
|
||||
<span className="text-xs text-muted-foreground">© {new Date().getFullYear()} {OPERATOR_NAME}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ThemeProvider } from "@/components/theme/theme-provider";
|
||||
import { ClerkThemeProvider } from "@/components/theme/clerk-theme-provider";
|
||||
import { RedditPixel } from "@/components/analytics/reddit-pixel";
|
||||
import {
|
||||
OPERATOR_NAME,
|
||||
SITE_DESCRIPTION,
|
||||
SITE_NAME,
|
||||
SITE_TAGLINE,
|
||||
@@ -36,9 +37,9 @@ export const metadata: Metadata = {
|
||||
},
|
||||
description: SITE_DESCRIPTION,
|
||||
applicationName: SITE_NAME,
|
||||
authors: [{ name: "Faradworks", url: "https://faradworks.com" }],
|
||||
creator: "Faradworks",
|
||||
publisher: "Faradworks",
|
||||
authors: [{ name: OPERATOR_NAME, url: SITE_URL }],
|
||||
creator: OPERATOR_NAME,
|
||||
publisher: SITE_NAME,
|
||||
category: "technology",
|
||||
keywords: [
|
||||
"schematic review",
|
||||
@@ -77,10 +78,11 @@ export const metadata: Metadata = {
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
site: TWITTER_HANDLE,
|
||||
creator: TWITTER_HANDLE,
|
||||
title: `${SITE_NAME} — ${SITE_TAGLINE}`,
|
||||
description: SITE_DESCRIPTION,
|
||||
...(TWITTER_HANDLE
|
||||
? { site: TWITTER_HANDLE, creator: TWITTER_HANDLE }
|
||||
: {}),
|
||||
},
|
||||
icons: {
|
||||
icon: [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Cpu, ArrowLeft } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { OPERATOR_NAME } from "@/lib/site";
|
||||
|
||||
type Tag = "New" | "Improved" | "Fixed";
|
||||
|
||||
@@ -170,7 +171,7 @@ export function ChangelogTimeline({ content }: { content: string }) {
|
||||
Terms of Service
|
||||
</Link>
|
||||
</div>
|
||||
<span>© {new Date().getFullYear()} Faradworks</span>
|
||||
<span>© {new Date().getFullYear()} {OPERATOR_NAME}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Cpu, ArrowLeft } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import { OPERATOR_NAME } from "@/lib/site";
|
||||
|
||||
function extractText(node: ReactNode): string {
|
||||
if (node == null || typeof node === "boolean") return "";
|
||||
@@ -133,7 +134,7 @@ export function LegalPageShell({ children }: { children: ReactNode }) {
|
||||
<Link href="/privacy" className="hover:text-foreground transition-colors">Privacy Policy</Link>
|
||||
<Link href="/terms" className="hover:text-foreground transition-colors">Terms of Service</Link>
|
||||
</div>
|
||||
<span>© {new Date().getFullYear()} Faradworks</span>
|
||||
<span>© {new Date().getFullYear()} {OPERATOR_NAME}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,14 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { authEnabled, localAuthEnabled, TOKEN_STORAGE_KEY, readAuthToken } from "@/lib/auth";
|
||||
import {
|
||||
authEnabled,
|
||||
localAuthEnabled,
|
||||
LEGACY_TOKEN_STORAGE_KEY,
|
||||
TOKEN_STORAGE_KEY,
|
||||
readAuthToken,
|
||||
writeAuthToken,
|
||||
} from "@/lib/auth";
|
||||
|
||||
export interface AppUser {
|
||||
id: string;
|
||||
@@ -42,8 +49,11 @@ function readStoredToken(): string | null {
|
||||
export function storeAuthToken(token: string | null) {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
if (token) window.localStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||||
else window.localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
if (token) writeAuthToken(token);
|
||||
else {
|
||||
window.localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
window.localStorage.removeItem(LEGACY_TOKEN_STORAGE_KEY);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
SkippedComponent,
|
||||
ValidationReport,
|
||||
} from "./types";
|
||||
import { projectVersionFromPayload } from "./pinscope-compat";
|
||||
|
||||
// Mirror of backend.periscopex.utils.safe_mpn — used to match MPNs
|
||||
// against datasheet filename stems returned by the server.
|
||||
@@ -102,10 +103,7 @@ function mapProject(p: Record<string, unknown>): Project {
|
||||
lcscToMpn: (p.lcsc_to_mpn as Record<string, string> | null) ?? null,
|
||||
lcscPayloads: (p.lcsc_payloads as Record<string, LcscPayload> | null) ?? null,
|
||||
componentMpns: (p.component_mpns as ComponentMpnBuckets | null) ?? null,
|
||||
periscopeVersion:
|
||||
(p.periscope_version as string | null | undefined) ??
|
||||
(p.pinscope_version as string | null | undefined) ??
|
||||
null,
|
||||
periscopeVersion: projectVersionFromPayload(p),
|
||||
netlistFormat: (p.netlist_format as Project["netlistFormat"]) ?? null,
|
||||
netlistSubdesigns: (p.netlist_subdesigns as string[] | null) ?? null,
|
||||
placementStatus: (p.placement_status as Project["placementStatus"]) ?? "draft",
|
||||
|
||||
+19
-12
@@ -7,6 +7,12 @@
|
||||
*
|
||||
* NEXT_PUBLIC_* vars are inlined at build time.
|
||||
*/
|
||||
import {
|
||||
LEGACY_TOKEN_STORAGE_KEY,
|
||||
TOKEN_STORAGE_KEY,
|
||||
migrateLocalKey,
|
||||
} from "@/lib/pinscope-compat";
|
||||
|
||||
export const authEnabled = Boolean(
|
||||
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ||
|
||||
process.env.NEXT_PUBLIC_AUTH_MODE === "local",
|
||||
@@ -16,23 +22,24 @@ export const localAuthEnabled =
|
||||
process.env.NEXT_PUBLIC_AUTH_MODE === "local" &&
|
||||
!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
export const TOKEN_STORAGE_KEY = "periscope_token";
|
||||
/** Pre-rebrand localStorage key — read once and migrate. */
|
||||
export const LEGACY_TOKEN_STORAGE_KEY = "pinscope_token";
|
||||
export { TOKEN_STORAGE_KEY, LEGACY_TOKEN_STORAGE_KEY };
|
||||
|
||||
export function readAuthToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const current = window.localStorage.getItem(TOKEN_STORAGE_KEY);
|
||||
if (current) return current;
|
||||
const legacy = window.localStorage.getItem(LEGACY_TOKEN_STORAGE_KEY);
|
||||
if (legacy) {
|
||||
window.localStorage.setItem(TOKEN_STORAGE_KEY, legacy);
|
||||
window.localStorage.removeItem(LEGACY_TOKEN_STORAGE_KEY);
|
||||
return legacy;
|
||||
}
|
||||
return migrateLocalKey(TOKEN_STORAGE_KEY, LEGACY_TOKEN_STORAGE_KEY);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeAuthToken(token: string): void {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
window.localStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||||
window.localStorage.removeItem(LEGACY_TOKEN_STORAGE_KEY);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Fase A compatibility fence for pre-rebrand PinScope identifiers.
|
||||
*
|
||||
* Write only Periscope keys (`periscope_token`, `periscopex:…`).
|
||||
* Read legacy `pinscope_*` / `pinscopex:` once, copy into the new key, then
|
||||
* drop the old key. Do not emit PinScope prefixes from new code.
|
||||
*
|
||||
* Docker network names (`pinscope_pinscope`) stay operational — see
|
||||
* docs/development/PINSCOPE_FENCE.md. Do not rename them in Fase A.
|
||||
*/
|
||||
|
||||
export const TOKEN_STORAGE_KEY = "periscope_token";
|
||||
export const LEGACY_TOKEN_STORAGE_KEY = "pinscope_token";
|
||||
|
||||
const NATIVE_PREFIX = "periscopex:";
|
||||
const LEGACY_PREFIX = "pinscopex:";
|
||||
|
||||
export function migrateLocalKey(newKey: string, legacyKey: string): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const current = localStorage.getItem(newKey);
|
||||
if (current != null) return current;
|
||||
const legacy = localStorage.getItem(legacyKey);
|
||||
if (legacy != null) {
|
||||
localStorage.setItem(newKey, legacy);
|
||||
localStorage.removeItem(legacyKey);
|
||||
return legacy;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function reviewedFindingsKey(projectId: string) {
|
||||
return `${NATIVE_PREFIX}reviewed-findings:${projectId}`;
|
||||
}
|
||||
|
||||
export function legacyReviewedFindingsKey(projectId: string) {
|
||||
return `${LEGACY_PREFIX}reviewed-findings:${projectId}`;
|
||||
}
|
||||
|
||||
export function deratingSettingsKey(projectId: string) {
|
||||
return `${NATIVE_PREFIX}derating-settings:${projectId}`;
|
||||
}
|
||||
|
||||
export function legacyDeratingSettingsKey(projectId: string) {
|
||||
return `${LEGACY_PREFIX}derating-settings:${projectId}`;
|
||||
}
|
||||
|
||||
export function deratingOverridesKey(projectId: string) {
|
||||
return `${NATIVE_PREFIX}derating-overrides:${projectId}`;
|
||||
}
|
||||
|
||||
export function legacyDeratingOverridesKey(projectId: string) {
|
||||
return `${LEGACY_PREFIX}derating-overrides:${projectId}`;
|
||||
}
|
||||
|
||||
/** Dual-read for project.json version stamped before the rebrand. */
|
||||
export function projectVersionFromPayload(p: {
|
||||
periscope_version?: string | null;
|
||||
pinscope_version?: string | null;
|
||||
}): string | null {
|
||||
return p.periscope_version ?? p.pinscope_version ?? null;
|
||||
}
|
||||
+18
-10
@@ -10,7 +10,13 @@ export const SITE_DESCRIPTION =
|
||||
|
||||
export const SITE_TAGLINE = "Agentic schematic validation";
|
||||
|
||||
export const TWITTER_HANDLE = "@getFaradWorks";
|
||||
/** Operator of this instance — not Faradworks, Inc. */
|
||||
export const OPERATOR_NAME = "Michele Bigi";
|
||||
|
||||
export const CONTACT_EMAIL = "mikbigi@gmail.com";
|
||||
|
||||
/** No Periscope X/Twitter account. Do not fall back to @getFaradWorks. */
|
||||
export const TWITTER_HANDLE: string | undefined = undefined;
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
@@ -30,6 +36,16 @@ export function pageMetadata({
|
||||
}): Metadata {
|
||||
const url = path.startsWith("/") ? path : `/${path}`;
|
||||
const fullTitle = `${title} · ${SITE_NAME}`;
|
||||
const twitter: NonNullable<Metadata["twitter"]> = {
|
||||
card: "summary_large_image",
|
||||
title: fullTitle,
|
||||
description,
|
||||
images: ["/twitter-image"],
|
||||
};
|
||||
if (TWITTER_HANDLE) {
|
||||
twitter.site = TWITTER_HANDLE;
|
||||
twitter.creator = TWITTER_HANDLE;
|
||||
}
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
@@ -50,14 +66,6 @@ export function pageMetadata({
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
site: TWITTER_HANDLE,
|
||||
creator: TWITTER_HANDLE,
|
||||
title: fullTitle,
|
||||
description,
|
||||
images: ["/twitter-image"],
|
||||
},
|
||||
twitter,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,10 @@
|
||||
/** Migrate localStorage keys from pre-rebrand `pinscopex:` prefix. */
|
||||
|
||||
export function migrateLocalKey(newKey: string, legacyKey: string): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const current = localStorage.getItem(newKey);
|
||||
if (current != null) return current;
|
||||
const legacy = localStorage.getItem(legacyKey);
|
||||
if (legacy != null) {
|
||||
localStorage.setItem(newKey, legacy);
|
||||
localStorage.removeItem(legacyKey);
|
||||
return legacy;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function reviewedFindingsKey(projectId: string) {
|
||||
return `periscopex:reviewed-findings:${projectId}`;
|
||||
}
|
||||
|
||||
export function legacyReviewedFindingsKey(projectId: string) {
|
||||
return `pinscopex:reviewed-findings:${projectId}`;
|
||||
}
|
||||
|
||||
export function deratingSettingsKey(projectId: string) {
|
||||
return `periscopex:derating-settings:${projectId}`;
|
||||
}
|
||||
|
||||
export function legacyDeratingSettingsKey(projectId: string) {
|
||||
return `pinscopex:derating-settings:${projectId}`;
|
||||
}
|
||||
|
||||
export function deratingOverridesKey(projectId: string) {
|
||||
return `periscopex:derating-overrides:${projectId}`;
|
||||
}
|
||||
|
||||
export function legacyDeratingOverridesKey(projectId: string) {
|
||||
return `pinscopex:derating-overrides:${projectId}`;
|
||||
}
|
||||
/** @deprecated Import from `@/lib/pinscope-compat`. Re-export kept for existing callers. */
|
||||
export {
|
||||
migrateLocalKey,
|
||||
reviewedFindingsKey,
|
||||
legacyReviewedFindingsKey,
|
||||
deratingSettingsKey,
|
||||
legacyDeratingSettingsKey,
|
||||
deratingOverridesKey,
|
||||
legacyDeratingOverridesKey,
|
||||
} from "@/lib/pinscope-compat";
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Fase A identity fence — dual-read PinScope issuers, write Periscope only."""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import jwt
|
||||
|
||||
from backend.pinscope_compat import JWT_ISSUER, LEGACY_JWT_ISSUERS
|
||||
from backend.services.local_jwt import ALGORITHM, decode_token, issue_token
|
||||
|
||||
|
||||
def test_issue_token_uses_periscope_issuer(monkeypatch):
|
||||
from backend.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "auth_jwt_secret", "unit-test-secret-32-bytes-min!!")
|
||||
token = issue_token("usr_test", "a@b.c")
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
"unit-test-secret-32-bytes-min!!",
|
||||
algorithms=[ALGORITHM],
|
||||
issuer=JWT_ISSUER,
|
||||
options={"verify_aud": False},
|
||||
)
|
||||
assert payload["iss"] == JWT_ISSUER
|
||||
assert payload["iss"] not in LEGACY_JWT_ISSUERS
|
||||
|
||||
|
||||
def test_decode_token_accepts_legacy_pinscope_issuer(monkeypatch):
|
||||
from backend.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "auth_jwt_secret", "unit-test-secret-32-bytes-min!!")
|
||||
now = datetime.now(timezone.utc)
|
||||
token = jwt.encode(
|
||||
{
|
||||
"sub": "usr_legacy",
|
||||
"email": "old@b.c",
|
||||
"iss": "pinscope-local",
|
||||
"iat": now,
|
||||
"exp": now + timedelta(days=1),
|
||||
},
|
||||
"unit-test-secret-32-bytes-min!!",
|
||||
algorithm=ALGORITHM,
|
||||
)
|
||||
decoded = decode_token(token)
|
||||
assert decoded is not None
|
||||
assert decoded["sub"] == "usr_legacy"
|
||||
Reference in New Issue
Block a user