diff --git a/.agents/skills/sync-bilingual-docs/SKILL.md b/.agents/skills/sync-bilingual-docs/SKILL.md index 608860a1..4dd4a9ed 100644 --- a/.agents/skills/sync-bilingual-docs/SKILL.md +++ b/.agents/skills/sync-bilingual-docs/SKILL.md @@ -23,14 +23,13 @@ Any change to these paths or their siblings: - `docs/product_strategy.md` / `product_strategy-tr.md` - `docs/roadmap.md` / `roadmap-tr.md` - `docs/reference/*.md` (has matching `-tr.md`) +- `docs/guides/*.md` and `docs/qms/*.md` (each has a `-tr.md` mirror; `docs/standards/localization.md` lists the exceptions) - `docs/usermanuals/{en,tr}/**/*.md` (the static-site SPA viewer source) Do **not** use for: - `docs/standards/*` (English only) - `docs/marketing/*` (gitignored working-memory; mixed languages) -- `docs/qms/*` (English only) - `docs/design/*` (English only) -- `docs/guides/*` (English only for now — Turkish is future work) Do **not** reference any file under gitignored working-memory directories (`docs/marketing/`, `docs/analysis/`) from the bilingual docs you edit — diff --git a/.claude/skills/sync-bilingual-docs/SKILL.md b/.claude/skills/sync-bilingual-docs/SKILL.md index 608860a1..4dd4a9ed 100644 --- a/.claude/skills/sync-bilingual-docs/SKILL.md +++ b/.claude/skills/sync-bilingual-docs/SKILL.md @@ -23,14 +23,13 @@ Any change to these paths or their siblings: - `docs/product_strategy.md` / `product_strategy-tr.md` - `docs/roadmap.md` / `roadmap-tr.md` - `docs/reference/*.md` (has matching `-tr.md`) +- `docs/guides/*.md` and `docs/qms/*.md` (each has a `-tr.md` mirror; `docs/standards/localization.md` lists the exceptions) - `docs/usermanuals/{en,tr}/**/*.md` (the static-site SPA viewer source) Do **not** use for: - `docs/standards/*` (English only) - `docs/marketing/*` (gitignored working-memory; mixed languages) -- `docs/qms/*` (English only) - `docs/design/*` (English only) -- `docs/guides/*` (English only for now — Turkish is future work) Do **not** reference any file under gitignored working-memory directories (`docs/marketing/`, `docs/analysis/`) from the bilingual docs you edit — diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c1fe2b6..d6ff8af7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -424,13 +424,15 @@ jobs: run: python3 tools/check_yaml_snippets.py --strict - name: User-manual YAML example schema-key drift - # check_yaml_snippets.py only validates snippets carrying the full - # model+training+data triplet; single-block fragments (the common - # case in a user-manual page explaining one section) are skipped - # there. This guard AST-walks forgelm/config.py's Pydantic schema - # and flags fragment keys that don't exist on it. Runs with --strict: - # the introduction-time fabricated-key backlog (38 instances across 8 - # EN/TR page-pairs) has been cleaned, so any new drift now fails CI. + # check_yaml_snippets.py validates only full configs (model + training + # + data) with real Pydantic. This guard reads every fenced YAML block + # in docs/usermanuals/ and fails on a block it cannot read (another + # fence spelling, an unclosed fence, a parse error, a duplicate key), + # on a fragment key path the schema AST-walked from forgelm/config.py + # does not declare, and on a top-level block ForgeConfig does not have + # (full configs included) unless the guard lists it as another tool's + # YAML or a known-open block; each listed row must match exactly one + # block per language, so a row that no longer matches fails too. run: python3 tools/check_usermanual_schema_drift.py --strict - name: Deprecation removal-target drift (strict) diff --git a/AGENTS.md b/AGENTS.md index ffe04563..d9d99a8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -231,7 +231,11 @@ Default workflow for a non-trivial change: `check_usermanual_schema_drift.py --strict` — validates that every fenced YAML key under `docs/usermanuals/` resolves against the real `ForgeConfig` schema, catching fabricated-field examples that would - fail `--dry-run`). The four after the import-origin guard are the + fail `--dry-run`; it also fails on a top-level block `ForgeConfig` does + not have — a `chat:` or `output:` example — unless the page and exact + key set are listed with a reason as another tool's YAML or as a + known-open block, and on a listed row that no longer matches). The four + after the import-origin guard are the historical gauntlet; the three doc guards (Wave 3 / Wave 4 / Wave 5 additions) catch bilingual structural drift, broken markdown anchors, and CLI ↔ docs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c1aa61b..1b9461b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,19 @@ All notable changes to ForgeLM are documented here. ### Changed +- `tools/check_usermanual_schema_drift.py` now also fails on a top-level block in a user-manual YAML example that `ForgeConfig` + does not have (a `chat:` or `output:` block that Pydantic rejects with `extra_forbidden`), including in a full-config summary + block, instead of skipping it as "someone else's YAML". Another tool's YAML (a CI workflow, a compose file, a vLLM config) is + listed with a reason in one table, a fabricated block that a tracked issue still owns in a second (its reason must name the + issue). A row exempts exactly one block per language directory and a row that matches nothing fails, so the tables cannot + become a blanket allowlist. A block the guard cannot read (a duplicate key, a parse error, an indented, `~~~` or `yml` fence, + a non-string key) now fails instead of passing silently, YAML is read with 1.2 booleans so GitHub Actions' `on:` stays a + string, a missing PyYAML exits 1, and the success line counts the blocks it found, walked and exempted (CR-A17a-007, #101). +- Help and message text only (behaviour, flags and exit codes are unchanged): `forgelm purge --kind artefacts` explains in its + error and `--help` that the standard compliance bundle has fixed filenames and is never matched; `forgelm purge` and + `forgelm reverse-pii` describe themselves as an input to an Article 15 response and one step of an Article 17 erasure; + `--output-dir`, `--dry-run`, `--config` and `--pii-ml-language` say what they do; the wizard prompts say "0 = never report"; + `forgelm ingest --pii-mask` no longer says "no PII detected" when the regex detectors find nothing (#101). - Admit newer majors of five dependencies: `huggingface_hub` 2, `pypdf` 6 and `xxhash` 4 (the `ingestion` extra), `datasketch` 2 (the `ingestion-scale` extra) and `pytest-randomly` 4 and 5 (the `dev` extra). The ingestion, data-audit and doctor test modules pass on pypdf 6.21.1, xxhash 4.0.1 and datasketch 2.0.0 with the same results as on the previous majors; CI installs datasketch 2.0.0 and @@ -112,6 +125,45 @@ All notable changes to ForgeLM are documented here. ### Fixed +- Make the GDPR, erasure, retention, PII-masking, GPU-cost and chat documentation say what ForgeLM does, so a deployer can copy a + documented command or YAML block, and quote a documented limit in a DPIA or an access reply, without it being untrue. The GDPR + / KVKK manual page was rewritten earlier in this cycle; this completes it and the pages around it (#101): + - Article 17(3)(b) is no longer presented as preserving the audit log as a class. It disapplies erasure only to the extent a + specific Union or Member State obligation (or a public-interest task) requires the processing, and keeping entries needs a + documented basis; ForgeLM never edits or deletes an entry. The AI Act examples that justified keeping logs are gone. + - `forgelm reverse-pii` is described as an input to an Article 15 response and `forgelm purge` as one step of an Article 17 + erasure, in the CLI help, the references, the manual, the compliance summary and the site (six languages). A complete response + also needs identity verification, the one-month deadline, a copy of the data and the 15(1)(a)-(h) information, Article 19 + notices and the 17(3) assessment; a scan with zero matches does not answer an access request. + - A `purge` digest (`target_id`) and a `reverse-pii` digest (`query_hash`) are byte-identical only for the same value, the same + `--output-dir` and the same `FORGELM_AUDIT_SECRET` state, in `reverse-pii`'s default plaintext mode. `--salt-source` is a + hash-mask scan for externally masked corpora: it is not needed for the digests to match and reports zero matches on a + plaintext corpus even when the person is there (an earlier draft recommended it). A salted digest is personal data, and + pseudonymisation (Art. 4(5)) only if the salt is kept separately: by default it sits next to the log. + - The retention horizons are limits that `forgelm purge --check-policy` reports and never enforces; the defaults (5 years for + the audit log, 7 / 90 / 90 days) are ForgeLM choices, not legal periods; a config with no `retention:` block checks nothing + and `retention: {}` switches the defaults on; `0` stops a report; ages come from the run's first audit event, else the log's + first event, else mtime, so a shared output directory can report a fresh bundle as overdue; ForgeLM creates neither + `raw_documents/` nor `ingestion_output/`. The "minimum 5 years" lines of the QMS SOPs follow the storage-limitation principle. + - The CI gate is `jq -e '.success == true and has("count") and .count == 0'` under `set -o pipefail` (the earlier forms were + green on a config error and on a directory that was never checked); argument-parser usage errors exit 2, not 1; the purge JSON + envelope section is regenerated from real output; the external-copies warning is row mode with `--config` only. + - `forgelm purge --kind artefacts` is documented as what it does: it matches files under `compliance/` whose names contain the + run id, so it finds nothing in the standard bundle (fixed basenames), emits `data.erasure_failed` (`NoMatchingArtefacts`), + exits 1 and deletes nothing for a real run id (a generic token that appears in a fixed name, such as `json`, does match those files and deletes them, so pass the full run id); the legacy `final_model.staging/` is purged only with a marker ForgeLM never writes. The code + half of that limitation stays open. + - The PII-masking page counts eight detectors; the German ID-card serial and the French NIR are shape-only (no checksum); the + audit report carries counts, not row indices; running `forgelm audit` over masked output re-applies the same regexes. The + `--pii-ml` examples drop the `organization` count Presidio does not report for English and use `--pii-ml-language xx` for + Turkish. The severity tiers cite Art. 87 and Art. 32; `include_eval_samples` cites Art. 25(2). + - The GPU-cost page describes what is recorded (device name, visible-device count, `train_runtime`, the real price table and + `cost_source` values), where it lands, the placeholder rate on machines without a CUDA GPU, a calibration recipe that runs, and + that `gpu_profiles` is not implemented. The chat page lists the real flags, says GGUF files are not loaded and that chat has + no seed or greedy mode, and shows the real transcript; `lora.target_modules` is a list. + - The site says masking is opt-in, that matching is literal and case-sensitive, and that `0` stops a retention report; the + German, French, Spanish and Chinese strings have no native review. The legal pages' date moved to 2026-10-22. + - Smaller corrections: KVKK Articles 7, 11(1)(e) and 13(2), the AI Act Art. 14(5) and Art. 18 statements, the human-oversight + timeout and quorum text, stale `compliance.py` line references, and the Turkish glossary (veri sahibi, veri sorumlusu, salt). - Fail closed when judge replies contain multiple score objects or duplicate JSON keys, and reject non-object replies, including a score object that only appears inside an array or after an undecodable `{`, with the existing judge-failure sentinel. The rejection reason is logged without echoing the reply diff --git a/CLAUDE.md b/CLAUDE.md index a41fd170..1855daf8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -231,7 +231,11 @@ Default workflow for a non-trivial change: `check_usermanual_schema_drift.py --strict` — validates that every fenced YAML key under `docs/usermanuals/` resolves against the real `ForgeConfig` schema, catching fabricated-field examples that would - fail `--dry-run`). The four after the import-origin guard are the + fail `--dry-run`; it also fails on a top-level block `ForgeConfig` does + not have — a `chat:` or `output:` example — unless the page and exact + key set are listed with a reason as another tool's YAML or as a + known-open block, and on a listed row that no longer matches). The four + after the import-origin guard are the historical gauntlet; the three doc guards (Wave 3 / Wave 4 / Wave 5 additions) catch bilingual structural drift, broken markdown anchors, and CLI ↔ docs diff --git a/config_template.yaml b/config_template.yaml index 415cc2c9..823b8e2a 100644 --- a/config_template.yaml +++ b/config_template.yaml @@ -268,12 +268,14 @@ data: # alert_on_drift: true # check_interval_hours: 24 -# Retention horizons — GDPR Article 5(1)(e) "storage limitation" + Article 17 -# "right to erasure". `staging_ttl_days` is the canonical staging-retention -# field (the legacy `evaluation.staging_ttl_days` alias was removed in v0.8.0). +# Retention horizons — operator-set limits that help implement GDPR Article 5(1)(e) +# "storage limitation" and support Article 17 erasure handling; the defaults are +# ForgeLM choices, not legal periods. `staging_ttl_days` is the canonical +# staging-retention field (the legacy `evaluation.staging_ttl_days` alias was +# removed in v0.8.0). # retention: -# audit_log_retention_days: 1825 # ~5 years (Article 12 record-keeping baseline) -# staging_ttl_days: 7 # final_model.staging./ TTL after `forgelm reject` +# audit_log_retention_days: 1825 # ~5 years (ForgeLM default, not a legal period) +# staging_ttl_days: 7 # age at which `purge --check-policy` reports final_model.staging./ overdue # ephemeral_artefact_retention_days: 90 # compliance bundles + audit reports # raw_documents_retention_days: 90 # ingested raw docs (PDF/DOCX/EPUB/TXT/MD) # enforce: "log_only" # reserved: no command acts on it yet ("log_only" | "warn_on_excess" | "block_on_excess") diff --git a/docs/design/gdpr_erasure.md b/docs/design/gdpr_erasure.md index 607ea70c..89085def 100644 --- a/docs/design/gdpr_erasure.md +++ b/docs/design/gdpr_erasure.md @@ -18,7 +18,7 @@ to be forgotten") + Article 5(1)(e) ("storage limitation"). ## 0. Why this document exists -ForgeLM emits training-data corpora, model adapters, audit logs, and derived artefacts that may contain personal data of identifiable EU data subjects. Article 17 of the GDPR gives those subjects the right to request that this data be erased. Article 5(1)(e) requires that any personal data not actively required for the declared purpose is removed by default after a defined retention horizon. +ForgeLM emits training-data corpora, model adapters, audit logs, and derived artefacts that may contain personal data of identifiable EU data subjects. Article 17 of the GDPR gives those subjects the right to request that this data be erased. Article 5(1)(e) requires that personal data be kept in identifiable form no longer than necessary for the purpose; together with Art. 25(2) and recital 39, controllers set time limits for erasure or periodic review. ForgeLM today has no operator-actionable tool for either obligation: @@ -43,11 +43,11 @@ Phase 21 closes the gap with a real implementation. This Phase 20 document spec | (e) Legal obligation | Court order. | (out of scope — operator handles externally; tool just records) | | (f) Child's data offered without parental consent | Subject was a minor at point of collection. | (out of scope — operator handles externally) | -**Article 17(2)** requires the controller to inform downstream processors / publishers of the erasure when the data has been disclosed. ForgeLM is not a publisher (we do not run inference); the deployer is. We document this boundary in the operator guide rather than implement push notification. +**Article 19** requires the controller to tell each recipient the data was disclosed to about the erasure, and **Article 17(2)** adds reasonable steps to inform other controllers where the controller made the data public. ForgeLM is not a publisher (we do not run inference); the deployer is. We document this boundary in the operator guide rather than implement push notification. -**Article 17(3)** lists exemptions where erasure does **not** apply — including (3)(b) compliance with a legal obligation of EU law, which is the entire reason the audit log itself is preserved. +**Article 17(3)** lists exemptions where erasure does **not** apply — including (3)(b) compliance with a legal obligation under Union or Member State law (or a task in the public interest or in the exercise of official authority) and (3)(e) the establishment, exercise or defence of legal claims; each applies only to the extent the processing is necessary for that obligation, task or claim, and the controller has to be able to name it. Erasure is also owed only where an Art. 17(1) ground exists, so entries may equally stay because the processing is still necessary for its purpose on its own Art. 6 basis; once the data subject withdraws consent or a successful objection removes that basis, it no longer carries them (Art. 17(1)(b)-(c)), and a refusal still needs reasons and a complaint route within the Art. 12 deadline (Art. 12(4)). -The take-away: erasure operates on **training data + derived artefacts**, not on the audit log. The audit log records *that an erasure happened* (Art. 5(2) accountability) but the original entries that reference the now-erased subject stay in place because the audit log is the legal-obligation record under Art. 17(3)(b). +The take-away: erasure operates on **training data + derived artefacts**, not on the audit log. The audit log records *that an erasure happened* (Art. 5(2) accountability), and ForgeLM never edits or deletes an entry. Whether the entries that reference the now-erased subject may stay is the controller's decision, and the controller documents why: they are still necessary for their purpose, or an Art. 17(3) exception applies (for (3)(b) and (3)(e), only to the extent a specific obligation or claim requires it). --- @@ -56,10 +56,10 @@ The take-away: erasure operates on **training data + derived artefacts**, not on | Artefact | Where it lives | Carries personal data? | Erasure strategy | |---|---|---|---| | **Training corpus rows** | `.jsonl` (operator-supplied) | Yes — the whole point | **Delete row in place** + audit event | -| **Audit log entries** | `/audit_log.jsonl` (append-only) | Indirectly (operator name, run config, possibly file paths that name a subject) | **Never delete original** — append a `data.erasure_completed` event that references the deleted row's `row_id`. Article 17(3)(b) exemption. | +| **Audit log entries** | `/audit_log.jsonl` (append-only) | Indirectly (operator name, run config, possibly file paths that name a subject) | **Never delete original** — append a `data.erasure_completed` event that references the deleted row's `row_id`. The tool never rewrites the chain; why the entries may be kept (still necessary for their purpose, or an Art. 17(3) exception) is the controller's documented decision. | | **Model adapters** | `/final_model/` (PEFT adapter + tokenizer) | **Memorised** training samples — extraction risk for high-overfit models | **Document escalation**: full retraining is the only proper resolution. Tool emits `data.erasure_warning_memorisation` audit event flagging the gap. | | **Final model staging** | `/final_model.staging./` | Same memorisation risk | Operator chooses: delete staging (pre-promotion) or treat as adapters above | -| **Compliance artefacts** | `/compliance/*.{json,yaml}` | References to corpus paths (could disclose subject names via filename) | **Regenerate** after erasure (or `forgelm purge --kind artefacts` rewrites them) | +| **Compliance artefacts** | `/compliance/*.{json,yaml}` | References to corpus paths (could disclose subject names via filename) | **Regenerate** after erasure (`forgelm purge --kind artefacts` is designed to remove them, but as shipped it does not find the standard bundle — §12) | | **Data audit reports** | `/data_audit_report.json` | `lang_sample` field carries up-to-512-char snippets from random rows (could include subject text) | **Regenerate** post-erasure (or hand-edit the snippets — the report is operator-facing only) | | **Synthetic / GRPO logs** | `/synthetic_data.jsonl`, GRPO completions | Yes if generated from a prompt that named the subject | **Delete row** with the same row-id mechanism as training corpus | | **Webhook history** | (external system; ForgeLM does not store) | Possibly | Out of scope — operator handles in the receiving system | @@ -72,6 +72,8 @@ The four artefact kinds the operator can act on in-tool: `corpus_rows`, `staging Article 5(1)(e) requires personal data to be kept "no longer than is necessary". ForgeLM ships a config block so the operator can declare their retention horizon and the tool can warn / refuse runs that violate it. +> **As shipped:** the horizons are only *reported* by `forgelm purge --check-policy` (read-only; exits 0 however many it finds). `retention.enforce` is reserved: no command acts on it (the training CLI only logs a warning when it is set), so nothing warns, refuses or deletes on age. Ages come from the audit log first (the run's first event, else the log's first event) and fall back to `mtime`. Read the warn / refuse and `mtime` passages below as the original design; §12 lists the deviations. + ### 3.1 Prior-state acknowledgement (audited 2026-05-02) Two pre-existing surfaces overlap with this design and must be resolved up front so Phase 21 does not ship a second source of truth: @@ -100,16 +102,13 @@ class RetentionConfig(BaseModel): model_config = ConfigDict(extra="forbid") - # Audit log retention horizon. EU AI Act Article 12(2) sets the floor - # at 6 months; Article 11 (technical documentation) extends to 10 years - # post-deployment for high-risk systems. GDPR Article 30 record-keeping - # commonly demands ≥3 years. We default to 1825 days (5 years) as a - # middle-of-the-road horizon that satisfies the AI Act floor with a - # comfortable margin AND covers the GDPR controller-record requirement. - # Operators with stricter compliance (Article 11 deployer obligation, - # ISO 27001 retention contracts) can set this higher; Article 12(2) - # operators with no AI Act obligation can lower to 180 (6 months). - # Set to 0 to disable horizon enforcement entirely (not recommended). + # Audit log retention horizon: the age at which `forgelm purge + # --check-policy` reports `audit_log.jsonl` as overdue. 1825 days + # (5 years) is a ForgeLM choice, not a legal period: GDPR Art. 5(1)(e) + # sets no fixed period. Align it with the obligations that apply to you + # (for example a sector retention rule, or AI Act Art. 18(1) for + # provider documentation once it applies to you) and with your own + # necessity assessment. Set to 0 to stop reporting it. audit_log_retention_days: int = Field(default=1825, ge=0) # Staging directory time-to-live. Article 14 staging is meant to hold a @@ -140,7 +139,7 @@ class RetentionConfig(BaseModel): enforce: Literal["log_only", "warn_on_excess", "block_on_excess"] = "log_only" ``` -`ForgeConfig` grows an optional `retention: Optional[RetentionConfig] = None` field. When unset, the existing default-permissive behaviour holds (no retention check). When set, the trainer's pre-flight gate runs the check. +`ForgeConfig` grows an optional `retention: Optional[RetentionConfig] = None` field. When unset, the existing default-permissive behaviour holds (no retention check). When set, `forgelm purge --check-policy` reports against it. The trainer pre-flight gate this section originally described was never built (§12). ### 3.3 mtime caveat (and the in-band age field) @@ -155,18 +154,18 @@ The `--check-policy` JSON output includes `age_source ∈ {audit, mtime}` per ar Three sites: -1. **`ForgeTrainer.train()` pre-flight** (before any data is read): scan `output_dir` for artefacts whose `mtime` exceeds the retention window; emit per-violation audit events; honour `enforce`. +1. **`ForgeTrainer.train()` pre-flight** *(designed; not implemented, see §12)* (before any data is read): scan `output_dir` for artefacts whose `mtime` exceeds the retention window; emit per-violation audit events; honour `enforce`. 2. **`forgelm purge --check-policy` (no --row-id / --run-id)**: read-only scan; reports violations as a structured summary (text + JSON output formats). -3. **`forgelm audit`** (existing subcommand): when `retention` is set in the loaded config, the audit report's `notes` block lists violations as a friendly heads-up. +3. **`forgelm audit`** (existing subcommand) *(designed; not implemented, see §12)*: when `retention` is set in the loaded config, the audit report's `notes` block lists violations as a friendly heads-up. ### 3.5 What "violation" means For each retention horizon: - Compare artefact `mtime` against `now - _days`. -- If older, the artefact has overstayed the declared lawful basis. -- The audit log itself is **never deleted automatically** even when the policy says it has overstayed — Article 17(3)(b) exemption applies and the operator must make the deletion call by hand. The tool only flags. +- If older, the artefact has passed the operator-declared retention horizon; review whether it is still necessary. +- The audit log itself is **never deleted automatically** even when the policy says it has overstayed. The tool only flags; the operator makes the deletion call by hand, after checking whether the entries are still necessary for their purpose or a specific Union or Member State obligation (Art. 17(3)(b)) or legal claim (Art. 17(3)(e)) still requires them. -The compliance artefacts (`compliance/*.json`) are tied to a specific run; their lifetime is the run's lifetime, not a separate horizon. In-place deletion is supported via `forgelm purge --run-id --kind artefacts` (§4.1) which removes `/compliance/*.json` for the named run while leaving the audit log + final model intact — useful when an operator wants to regenerate the bundle after an Article 17 corpus erasure changed the lineage. Whole-`` deletion remains the operator's manual escalation when the entire run is being scrubbed. +The compliance artefacts (`compliance/*.json`) are tied to a specific run; their lifetime is the run's lifetime, not a separate horizon. In-place deletion is specified via `forgelm purge --run-id --kind artefacts` (§4.1) which is meant to remove `/compliance/*.json` for the named run while leaving the audit log + final model intact (as shipped, the resolver only matches filenames that embed the run id, and the standard bundle uses fixed basenames, so it finds nothing — see the purge reference) — useful when an operator wants to regenerate the bundle after an Article 17 corpus erasure changed the lineage. Whole-`` deletion remains the operator's manual escalation when the entire run is being scrubbed. --- @@ -190,7 +189,7 @@ A single invocation does exactly one of the three. Combining flags is a `Config | `--corpus ` | corpus mode | str | Path to **a single JSONL file**. Directory mode is **rejected** in `--row-id` mode — multi-file purges are an operator script (loop over files), not a `forgelm purge` flag, because GDPR Article 17 expects an erasure decision *per row* with its own audit event. An invocation that matches the same id in two files is a "DELETE without WHERE" hazard. | | `--row-matches` | corpus mode optional | enum | `one` (default; refuses if 0 or ≥2 matches in the file) / `all` (deletes every match, requires explicit opt-in for the ambiguity). Without this flag, multi-row matches in a single file abort with `EXIT_CONFIG_ERROR` so the operator confirms intent before bulk delete. | | `--run-id ` | run mode | str | Run id matching `/audit_log.jsonl`'s top-level `run_id`. | -| `--kind {staging,artefacts}` | run mode | enum | Which derived artefacts to erase. `staging` = `final_model.staging.*`; `artefacts` = `compliance/`, `data_audit_report.json`, derived JSONL snapshots. | +| `--kind {staging,artefacts}` | run mode | enum | Which derived artefacts to erase. `staging` = `final_model.staging.*`; `artefacts` = `compliance/`, `data_audit_report.json`, derived JSONL snapshots (design intent; what shipped is in §12). | | `--output-dir ` | run mode | str | Where to look for the run. Default `./` (current dir). | | `--check-policy` | policy mode | bool | Dry-run scan; reports violations against the loaded config's `retention` block. | | `--config ` | policy mode (optional) | str | Config to load for the retention block; defaults to `./forgelm.yaml` then walks up. | @@ -206,12 +205,12 @@ Standard ForgeLM 0/1/2/3/4 contract — all codes from `forgelm.cli._exit_codes` | Code | Constant | Meaning | |---|---|---| | 0 | `EXIT_SUCCESS` | Erasure completed (or `--dry-run` reported successfully). | -| 1 | `EXIT_CONFIG_ERROR` | Config-level error: missing flag, flag combination, unknown row-id (corpus mode), unknown run-id (run mode), `--row-id` directory mode without `--row-matches=all`, multi-row match without `--row-matches=all`. (`--yes` is not implemented — see §3.5 for the consent model; the shipped `forgelm purge` is non-interactive on every invocation, so there is no "missing `--yes` on a non-TTY" failure mode.) | +| 1 | `EXIT_CONFIG_ERROR` | Config-level error: no mode flag, `--run-id` without `--kind`, `--row-id` without `--corpus`, a directory passed as `--corpus` (rejected even with `--row-matches=all`), unknown row-id (corpus mode), unknown run-id (run mode), multi-row match without `--row-matches=all`. Flag combinations the argument parser rejects exit 2 instead (§12). (`--yes` is not implemented — see §3.5 for the consent model; the shipped `forgelm purge` is non-interactive on every invocation, so there is no "missing `--yes` on a non-TTY" failure mode.) | | 2 | `EXIT_TRAINING_ERROR` | Runtime / I/O failure: corpus unreadable, audit-log write failure, atomic-rename failure, partial commit recovery. | -| 3 | `EXIT_EVAL_FAILURE` | Reserved for the trainer pre-flight gate when `enforce: block_on_excess` is configured (see §3.4 in the spirit of the master `error-handling.md` exit-code contract). **Not** used by `forgelm purge --check-policy` — that path is a report, not a gate; see §10 Q5 + the paragraph below. | +| 3 | `EXIT_EVAL_FAILURE` | Reserved for the trainer pre-flight gate when `enforce: block_on_excess` is configured (no such gate exists today; see §3.4 in the spirit of the master `error-handling.md` exit-code contract). **Not** used by `forgelm purge --check-policy` — that path is a report, not a gate; see §10 Q5 + the paragraph below. | | 4 | `EXIT_AWAITING_APPROVAL` | Reserved for its trainer-pipeline meaning per `docs/standards/error-handling.md` ("Training + evals passed, but `require_human_approval: true` — staged, awaiting human sign-off"). **Not** reused by `forgelm purge`. Since the shipped `forgelm purge` is non-interactive (no prompt, no `--yes` flag), there is no "operator declined the prompt" path; consent is recorded in the operator-supplied `--justification` and the audit chain. | -**`--check-policy` always exits 0** (per §10 Q5 — the resolved decision: report-not-gate semantic). Operators who want a CI gate use `--output-format json` and pipe to `jq '.violations | length'` themselves; CI then branches on that count rather than on the exit code. This keeps the public contract `docs/standards/error-handling.md` (0/1/2/3/4 trainer + 5 wizard-cancel) consistent across every ForgeLM subcommand: a code-3 from `forgelm purge` always means "trainer pre-flight gate failed", never "report found something". +**`--check-policy` exits 0 on a successful report** (per §10 Q5 — the resolved decision: report-not-gate semantic). Operators who want a CI gate use `--output-format json` and test the report themselves (for example `jq -e '.success == true and has("count") and .count == 0'` under `set -o pipefail`; `count` is absent when no `retention:` block was loaded); CI then branches on that rather than on the exit code. This keeps the public contract `docs/standards/error-handling.md` (the exit-code table in that standard) consistent across every ForgeLM subcommand: `forgelm purge` never exits 3 (the trainer pre-flight gate that would have used it was never built), so a code 3 can never mean "report found something". ### 4.4 Atomicity @@ -224,13 +223,13 @@ Standard ForgeLM 0/1/2/3/4 contract — all codes from `forgelm.cli._exit_codes` 5. `os.fsync` + `os.replace` to swap. 6. **Audit `data.erasure_completed` LAST** — same fields plus `bytes_freed`, `pre_erasure_line_number`, `files_modified=[corpus_path]`. -Recovery path for a step-5 failure (rename fails after the temp file is fully written): the `data.erasure_requested` event is in the chain, the temp file is left on disk for forensic inspection, and a `data.erasure_failed` event is appended carrying the original error. An operator inspecting the log sees the request → failure pair and can manually decide whether to retry or revert. +Recovery path for a step-5 failure (rename fails after the temp file is fully written): the `data.erasure_requested` event is in the chain, the temp file is removed on a best-effort basis (the corpus itself is untouched), and a `data.erasure_failed` event is appended carrying the original error. An operator inspecting the log sees the request → failure pair and can manually decide whether to retry or revert. **Recovery path for a step-6 failure** (rename succeeded, completion event write fails): the row is gone from the corpus but the chain has only the request event. This is detectable: a `request` event without a matching `completed` / `failed` event within the same minute is the operator's signal to verify the corpus state and append a manual `data.erasure_completed` (or `_failed`) event by hand. Phase 21 ships a `tests/test_gdpr_erasure.py::test_partial_commit_emits_failed_event` test that simulates this race + asserts the chain stays interpretable. **Run-scoped erasure** for `kind=staging` uses `shutil.rmtree(staging_path)` after a `os.path.commonpath` check (same defence-in-depth as `_approve.py`'s staging path validator) so an attacker who can rewrite the audit log cannot point us at `/etc`. -**Run-scoped erasure** for `kind=artefacts` deletes a fixed allow-list (`compliance/`, `data_audit_report.json`, `data_governance_report.json`) — never a glob. Allow-list lives in `forgelm.compliance` next to the artefact emitter. +**Run-scoped erasure** for `kind=artefacts` is designed to delete a fixed allow-list (`compliance/`, `data_audit_report.json`, `data_governance_report.json`) — never a glob — kept in `forgelm.compliance` next to the artefact emitter. That allow-list does not exist yet; §12 describes what shipped. ### 4.5 What `forgelm purge` does NOT do @@ -239,7 +238,7 @@ Recovery path for a step-5 failure (rename fails after the temp file is fully wr | **Re-train the model after corpus erasure** | Operator decision; ForgeLM cannot infer the right downstream pipeline. | | **Distributed corpus deletion** | Single-node only. Multi-node corpus shards live on separate filesystems; the operator runs `forgelm purge --row-id` on each. | | **Webhook receiver cleanup** | The Slack / Teams thread that received `notify_awaiting_approval` is in the operator's chat platform, not in ForgeLM. We log a notice. | -| **Audit log rewriting** | The chain is append-only by construction (Art. 17(3)(b) exemption). Erasure adds an event; never edits an entry. | +| **Audit log rewriting** | The chain is append-only by construction. Erasure adds an event; never edits an entry. Whether the entries may be kept is the controller's documented call: still necessary for their purpose, or an Art. 17(3) exception such as (3)(b) / (3)(e), which applies only to the extent a specific obligation or claim requires it. | | **Model weight forgetting** | "Machine unlearning" is an active research area; we do not ship a half-baked impl. We emit `data.erasure_warning_memorisation` so the operator knows the gap is theirs to close. | --- @@ -257,9 +256,9 @@ Six new events in `audit_event_catalog.md` — three core erasure events plus th | `data.erasure_requested` | First step of any `forgelm purge --row-id` / `--run-id` invocation, *before* any deletion (see §4.4 commit ordering). If deletion then fails, the request record stays in the chain so a forensic reviewer can see the intent. `--check-policy` is read-only and does not emit this event. | `target_kind` ∈ {row, staging, artefacts}, `target_id` (hashed per §5.4 for row mode), `salt_source` ∈ {`"env_var"`, `"per_dir"`} (row mode only; see §5.4), `corpus_path` (corpus mode), `output_dir` (run mode), `justification`, `dry_run` | | `data.erasure_completed` | Successful deletion finishes. | All `data.erasure_requested` fields + `bytes_freed`, `files_modified` (list), `pre_erasure_line_number` (corpus mode) | | `data.erasure_failed` | Deletion failed (atomic rename failure, permission denied, I/O error). | All `data.erasure_requested` fields + `error_class`, `error_message` (already-redacted via §5.4 policy + `_http`'s mask helper) | -| `data.erasure_warning_memorisation` | Emitted alongside `data.erasure_completed` when `target_kind="row"` AND a `/final_model/` exists for any run that consumed this corpus. Signals that the row is gone from the corpus but may still be memorised in trained weights. | All `data.erasure_completed` fields + `affected_run_ids` (list of run ids whose final_model used this corpus), `recommendation` (literal `"full retraining without the erased row is the only proper mitigation"`) | +| `data.erasure_warning_memorisation` | Emitted alongside `data.erasure_completed` when `target_kind="row"` AND a `/final_model/` exists. The trigger is that directory's existence alone: ForgeLM does not check which run (if any) trained on this corpus or this row. Signals that the row is gone from the corpus but may still be memorised in trained weights. | All `data.erasure_completed` fields + `affected_run_ids` (list of run ids whose final_model used this corpus), `recommendation` (literal `"full retraining without the erased row is the only proper mitigation"`) | | `data.erasure_warning_synthetic_data_present` | Emitted alongside `data.erasure_completed` when `target_kind="row"` AND any `/synthetic_data*.jsonl` exists. Signals that the row may have produced derivative synthetic snippets that the row→snippet mapping no longer connects. | All `data.erasure_completed` fields + `synthetic_files` (list of paths), `recommendation` (literal `"regenerate synthetic data after erasure to ensure no derivatives reference the erased row"`) | -| `data.erasure_warning_external_copies` | Emitted alongside `data.erasure_completed` when the loaded config has a non-empty `webhook` block. Signals that downstream consumers may have received notices that referenced the now-erased data; ForgeLM's local erasure does not propagate. | All `data.erasure_completed` fields + `webhook_targets` (list of redacted URLs), `recommendation` (literal `"propagate the erasure to downstream processors per Article 17(2)"`) | +| `data.erasure_warning_external_copies` | Emitted alongside `data.erasure_completed` in row mode when `--config` is given and its `webhook` block names a target (a literal `url`, or a `url_env` variable exported in the purge environment). Signals that downstream consumers may have received notices that referenced the now-erased data; ForgeLM's local erasure does not propagate. | All `data.erasure_completed` fields + `webhook_targets` (list of redacted URLs), `recommendation` (literal `"tell the recipients the data was disclosed to about the erasure, per Article 19 (and Article 17(2) where the data was made public)"`) | ### 5.2 Chain-integrity post-erasure @@ -280,7 +279,7 @@ If the **justification** field includes something that *might* be PII (e.g. oper ### 5.4 Personal-data minimisation across every audit-event field -Article 17(3)(b) preserves the audit log itself, but Article 5(1)(c) (data minimisation) still applies to **what we put in each event**. Phase 21 enumerates every field on every new audit event and classifies it; categories that may carry personal data get a hash / redaction policy at emit time so the persisted log carries the minimum identifying surface needed for accountability. +The audit log itself is append-only, but Article 5(1)(c) (data minimisation) still applies to **what we put in each event**. Phase 21 enumerates every field on every new audit event and classifies it; categories that may carry personal data get a hash / redaction policy at emit time so the persisted log carries the minimum identifying surface needed for accountability. | Event field | Source / shape | Personal-data category | Phase 21 policy | |---|---|---|---| @@ -297,7 +296,7 @@ Article 17(3)(b) preserves the audit log itself, but Article 5(1)(c) (data minim | `pre_erasure_line_number` | int | None | clear | | `error_class` | exception class name | None | clear | | `error_message` | str | **Possibly personal** (Python tracebacks include `repr()` of objects, which may include row content) | route through `forgelm._http._mask_secrets_in_text` first; redact obvious PII (email regex, phone regex) via the existing `forgelm.data_audit._pii_regex` mask helpers | -| `operator` (carried on every AuditLogger event) | `FORGELM_OPERATOR` or `getuser()@host` | **Personal** | clear (Article 12 record-keeping requires identifiable operator; this is the explicit Article 5(1)(b) lawful basis) | +| `operator` (carried on every AuditLogger event) | `FORGELM_OPERATOR` or `getuser()@host` | **Personal** | clear (the record must say who acted, so the operator stays identifiable; the controller needs its own documented Article 6 basis for keeping that identity in the log) | The take-away: **the only field this design hashes by default is `target_id` in row mode**. Everything else either is genuinely operational, is the operator's responsibility (justification), or is masked through the existing Wave 1 `_mask_secrets_in_text` helper. Phase 21 ships a unit test `tests/test_gdpr_erasure.py::test_target_id_hashed_in_audit_event` that asserts the raw row id never appears in the persisted JSONL after a `forgelm purge --row-id ali@example.com --corpus train.jsonl` run. @@ -322,9 +321,10 @@ in three scopes: 1. **Training corpus row erasure.** `forgelm purge --row-id --corpus ` atomically rewrites the JSONL, dropping the matched row. 2. **Run-scoped artefact erasure.** `forgelm purge --run-id - --kind {staging,artefacts}` deletes the model staging directory or the - compliance bundle for a specific run. -3. **Retention policy enforcement.** Configure `retention.*` in your YAML; + --kind {staging,artefacts}` deletes the model staging directory for a + specific run and the compliance files whose names embed the run id (as + shipped, not the standard bundle; see the purge reference). +3. **Retention reporting.** Configure `retention.*` in your YAML; `forgelm purge --check-policy` lists artefacts that have overstayed the declared horizon. @@ -354,7 +354,7 @@ What ForgeLM does **not** do: - Forgetting from already-trained model weights (this is an active research area called "machine unlearning"; the only proper mitigation today is full retraining without the erased data). -- Pushing erasure notices to downstream processors (Article 17(2)) — that +- Pushing erasure notices to downstream recipients (Article 19; Article 17(2) where the data was made public) — that is your runtime layer's responsibility, not the training toolkit's. See [`docs/guides/gdpr_erasure.md`](../guides/gdpr_erasure.md) for the operator how-to @@ -375,7 +375,7 @@ Phase 21 ships **at least 7 tests** covering the acceptance surface. Every test | 1 | `test_row_erasure_rewrites_jsonl_atomically` | Row matching `row_id=42` removed from `train.jsonl`; other 99 rows byte-identical to pre-erasure. | | 2 | `test_row_erasure_emits_audit_events` | Two new audit events landed: `data.erasure_requested` then `data.erasure_completed`, in that order, with matching `target_id` field. | | 3 | `test_run_scoped_staging_erasure` | `--run-id fg-abc --kind staging` removes `final_model.staging.fg-abc/` recursively; final dir untouched if it exists. | -| 4 | `test_run_scoped_artefacts_erasure` | `--run-id fg-abc --kind artefacts` removes the `compliance/` allow-list; `final_model/` untouched. | +| 4 | `test_run_scoped_artefacts_erasure` | `--run-id fg-abc --kind artefacts` removes `compliance/` files named for the run; `final_model/` untouched (the standard fixed-basename bundle is not found, pinned separately). | | 5 | `test_audit_chain_verifies_post_erasure` | After erasure, `verify_audit_log(path).valid is True`; chain length grew by 2 events. | | 6 | `test_check_policy_reports_violations` | `--check-policy` against a fixture with overstayed artefacts returns the correct violation list (text + JSON). | | 7 | `test_unknown_row_id_clear_error` | Unknown `--row-id` exits 1 with a message that names the file scanned and the id searched. | @@ -444,7 +444,7 @@ The closure plan §15.5 lists three open items that intersect Phase 21: ### Q5. Should `--check-policy` return non-zero when violations are found? -**Decision:** no. `--check-policy` is a *report*, not a *gate*. The CI gate is the trainer pre-flight (when `enforce: block_on_excess` is configured). Mixing gate + report semantics into one exit code makes the tool harder to script. Operators who want a CI gate use `--output-format json` and pipe to `jq '.violations | length'` themselves. +**Decision:** no. `--check-policy` is a *report*, not a *gate*. The trainer pre-flight gate that was meant to be the CI gate (`enforce: block_on_excess`) was never built, so CI tests the JSON report. Mixing gate + report semantics into one exit code makes the tool harder to script. Operators who want a CI gate use `--output-format json` and test the report themselves (see §4.3). ### Q6. Should we delete the synthetic-data snapshots on row erasure? @@ -465,10 +465,24 @@ The closure plan §15.5 lists three open items that intersect Phase 21: ## 12. Implementation status -The design above shipped end-to-end in v0.5.5: +The design above shipped in v0.5.5, with the deviations listed after this list: -- **`RetentionConfig`** (§3): pinned in `forgelm/config.py` as a top-level `retention:` block with five fields (`audit_log_retention_days`, `staging_ttl_days`, `ephemeral_artefact_retention_days`, `raw_documents_retention_days`, `enforce`). `EvaluationConfig.staging_ttl_days` deprecated with `DeprecationWarning` + alias-forwarding to `retention.staging_ttl_days`. +- **`RetentionConfig`** (§3): pinned in `forgelm/config.py` as a top-level `retention:` block with five fields (`audit_log_retention_days`, `staging_ttl_days`, `ephemeral_artefact_retention_days`, `raw_documents_retention_days`, `enforce`). `EvaluationConfig.staging_ttl_days` was deprecated with `DeprecationWarning` + alias-forwarding to `retention.staging_ttl_days` and has since been removed (v0.8.0); §3.1 describes that historical window. - **`forgelm purge` subcommand** (§4): pinned in `forgelm/cli/subcommands/_purge.py` with `--row-id`, `--corpus`, `--run-id`, `--kind`, `--check-policy`, atomic-rewrite + recovery semantics. - **Audit events** (§5): six new events live in `audit_event_catalog.md` — `data.erasure_requested`, `data.erasure_completed`, `data.erasure_failed`, plus three operator-warning events. - **Tests** (§7): `tests/test_gdpr_erasure.py` ships the regression coverage (unknown row id, multi-match refusal, partial-commit recovery, retention horizon enforcement, audit-PII redaction, etc.). - **User-facing docs** (§6, §8): `docs/guides/gdpr_erasure{,-tr}.md` + `docs/usermanuals/{en,tr}/compliance/gdpr-erasure.md` reference the implemented surface. + +**Deviations from the design, as shipped today:** + +- `--kind artefacts` matches `compliance/` filenames that embed the run id as a token. The exporter writes fixed basenames (`compliance_report.json`, `training_manifest.yaml`, ...), so on a standard output directory the command finds nothing: it fails safe (nothing is deleted, `data.erasure_failed` with `NoMatchingArtefacts`, exit 1). The fixed allow-list that §4.4 describes, plus an ownership check, is open work. +- Retention horizons are reported by `forgelm purge --check-policy` and never enforced; `retention.enforce` is reserved and no command acts on it (the training CLI logs a warning when it is set). There is no trainer pre-flight gate: nothing exits 3 or refuses a run on age. +- The legacy `final_model.staging/` is purged only when it holds a `staging_run_id` marker equal to the run id. Nothing in ForgeLM writes that marker. +- `--check-policy` ages the raw-documents directories (`raw_documents/`, `ingestion_output/`) from the audit log's first event, not per run; it reads `--config` only when given (no `./forgelm.yaml` discovery) and, without a `retention:` block, reports that nothing was checked. `--dry-run` combined with `--check-policy` is not rejected. +- The `data.erasure_warning_external_copies` event is emitted in row mode only, and only when `--config` is given; run mode never loads the config. +- Mode flags that conflict (for example `--row-id` with `--run-id`, or `--kind logs`) are rejected by the argument parser, which exits 2 without a JSON envelope; other configuration errors exit 1. A run id is not looked up in the audit log: it is "unknown" only when no matching artefact exists. +- With `FORGELM_AUDIT_SECRET` set, the per-directory salt is XOR-combined with the first 16 bytes of the secret (`salt_source=env_var`), so a row's `target_id` changes when the secret rotates. `forgelm reverse-pii` derives its `query_hash` the same way in its default plaintext mode; `--salt-source` switches it to a hash-mask scan that looks for digests inside the corpus. +- `data.erasure_warning_memorisation` fires whenever `/final_model/` exists; the check does not tie that model to the erased row. +- The recovery paths of §4.4 assume the temp file survives a failed rename; the shipped code removes it (best effort) instead. +- Neither the trainer pre-flight gate nor the `forgelm audit` retention notes of §3.4 exist. +- The `recommendation` field listed for the new events in §5.1 is not emitted, and the test table in §7 predates the shipped tests (for example, a dry-run appends audit events, and erasing the same row twice exits 1 with `NoMatchingRow`); `tests/test_gdpr_erasure.py` is the source of truth. diff --git a/docs/guides/data_audit-tr.md b/docs/guides/data_audit-tr.md index 74386f96..e631566d 100644 --- a/docs/guides/data_audit-tr.md +++ b/docs/guides/data_audit-tr.md @@ -160,9 +160,16 @@ kötü?* sorusunda sıfır rehberlik veriyor. Faz 11.5 yanına bir } ``` -Tier tablosu konsensüs regülatif ağırlıklandırmadır (finansal -kimlikler için PCI-DSS; devlet kimlikleri için GDPR Md. 9 + ENISA). -`critical` katman artık komutun kendisini kapılar (sonraki bölüm); +Tier tablosu ForgeLM'in varsayılan ağırlıklandırmasıdır; herhangi bir +mevzuattan alınmış bir sınıflandırma değildir. Kart numaraları ve IBAN'lar +doğrudan dolandırıcılıkta kullanılabildikleri için `critical` katmandadır +(PCI DSS, yasa değil sektör standardı, kart numaralarını kapsar; IBAN'lar aynı +katmanda ForgeLM'in kendi değerlendirmesiyle yer alır). Ulusal kimlik +numaraları değiştirilmesi zor olduğu ve bazı ulusal mevzuatlar kullanımlarını +kısıtladığı için `high` katmandadır (GDPR Madde 87, üye devletlerin ulusal +kimlik numarasının işlenme koşullarını belirlemesine izin verir). GDPR hiçbir +tanımlayıcıyı sıralamaz: Madde 32 riske uygun güvenlik ister; bu tablo o +değerlendirmeye başlamanıza yardım eder, yerini tutmaz. `critical` katman artık komutun kendisini kapılar (sonraki bölüm); `high`'ta da durmak isteyen pipeline'lar `pii_severity.worst_tier`'i okumalı ve açık bir review olmadan yayınlamayı reddetmelidir. @@ -406,8 +413,12 @@ install ile uyumlu kalır. ### ML-NER PII adaptörü — `--pii-ml` (Faz 12.5, opt-in) -Varsayılan regex detector, EU AI Act Madde 10'un önemsediği yapılandırılmış -identifier'ları (email, telefon, IBAN, kredi kartı, ulusal kimlik) kapsar. +Varsayılan regex detector, yaygın yapılandırılmış identifier'ları (email, +telefon, IBAN, kredi kartı, ulusal kimlik) kapsar. Her biri, kimliği belirli +veya belirlenebilir bir gerçek kişiye ilişkin olduğunda GDPR Madde 4(1) +kapsamında kişisel veridir; bir tüzel kişinin IBAN'ı veya santral numarası, şahıs tacir gibi bir gerçek +kişiyi tanımlamadıkça kişisel veri değildir (Gerekçe 14); bir test kart +numarası ise hiç kimseye ilişkin değildir. Faz 12.5, regex'in doğal olarak kaçırdığı yapılandırılmamış identifier kategorilerini ekleyen opt-in **Presidio** ([data-privacy-stack/presidio](https://github.com/data-privacy-stack/presidio)) adaptörünü ML-NER olarak üstüne ekler: `person`, `organization`, @@ -420,7 +431,8 @@ forgelm audit data/ --output ./audit/ --pii-ml Yeni kategoriler mevcut `pii_summary` ve `pii_severity` bloklarına disjoint isimlerle birleşir, bu yüzden regex baseline ML sinyaliyle -yan yana görünür kalır: +yan yana görünür kalır (örnek ForgeLM'in varsayılan İngilizce yapılandırması +içindir; o yapılandırma `organization` raporlamaz): ```json { @@ -428,15 +440,14 @@ yan yana görünür kalır: "email": 12, "phone": 3, "person": 47, // ← Presidio - "organization": 18, // ← Presidio "location": 9 // ← Presidio }, "pii_severity": { - "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 30}, + "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 12}, "by_type": { "email": {"count": 12, "tier": "medium"}, "person": {"count": 47, "tier": "medium"}, - "organization": {"count": 18, "tier": "low"} + "location": {"count": 9, "tier": "low"} }, "worst_tier": "medium" } diff --git a/docs/guides/data_audit.md b/docs/guides/data_audit.md index 81dd7e04..746f5995 100644 --- a/docs/guides/data_audit.md +++ b/docs/guides/data_audit.md @@ -150,9 +150,15 @@ The flat `pii_summary` map gives compliance reviewers no guidance on } ``` -The tier table is consensus regulatory weighting (PCI-DSS for financial -identifiers; GDPR Art. 9 + ENISA for government IDs). The `critical` -tier now gates the command itself (next section); pipelines that also +The tier table is ForgeLM's default weighting, not a classification taken +from any regulation. Card numbers and IBANs are `critical` because they are +directly usable for fraud (PCI DSS, an industry standard, covers card +numbers; IBANs share the tier as ForgeLM's own judgement). National +identifiers are `high` because they are hard to change and some national +laws restrict their use (GDPR Art. 87 lets Member States set conditions for +processing a national identification number). GDPR itself ranks no +identifier: Art. 32 asks for security appropriate to the risk, an assessment +this table helps you start and does not replace. The `critical` tier now gates the command itself (next section); pipelines that also want to stop at `high` should read `pii_severity.worst_tier` and refuse to publish without explicit review. @@ -400,8 +406,10 @@ the audit reproducible (Annex IV requirement) and bare-install-friendly. ### ML-NER PII adapter — `--pii-ml` (Phase 12.5, opt-in) -The default regex detector covers the structured identifiers EU AI Act -Article 10 cares about (email, phone, IBAN, credit card, national IDs). +The default regex detector covers the common structured identifiers +(email, phone, IBAN, credit card, national IDs). Each is personal data under +GDPR Art. 4(1) when it relates to an identified or identifiable natural +person; a legal person's IBAN or switchboard number is not (recital 14) unless it identifies a natural person such as a sole trader, and a test card number relates to no one. Phase 12.5 adds an opt-in **Presidio** ([data-privacy-stack/presidio](https://github.com/data-privacy-stack/presidio)) adapter that layers ML-NER on top — adding the unstructured-identifier categories regex inherently misses: `person`, `organization`, `location`. @@ -413,7 +421,8 @@ forgelm audit data/ --output ./audit/ --pii-ml The new categories merge into the existing `pii_summary` and `pii_severity` blocks under disjoint names so the regex baseline stays -visible alongside the ML signal: +visible alongside the ML signal (the example is for ForgeLM's default English +configuration, which never reports `organization`): ```json { @@ -421,15 +430,14 @@ visible alongside the ML signal: "email": 12, "phone": 3, "person": 47, // ← Presidio - "organization": 18, // ← Presidio "location": 9 // ← Presidio }, "pii_severity": { - "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 30}, + "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 12}, "by_type": { "email": {"count": 12, "tier": "medium"}, "person": {"count": 47, "tier": "medium"}, - "organization": {"count": 18, "tier": "low"} + "location": {"count": 9, "tier": "low"} }, "worst_tier": "medium" } diff --git a/docs/guides/gdpr_erasure-tr.md b/docs/guides/gdpr_erasure-tr.md index 7fa432c1..7027a49f 100644 --- a/docs/guides/gdpr_erasure-tr.md +++ b/docs/guides/gdpr_erasure-tr.md @@ -3,26 +3,30 @@ > **AB Genel Veri Koruma Tüzüğü Madde 17** ("unutulma hakkı") veri > sahibine, sorumlunun kendisi hakkında tuttuğu kişisel verilerin > silinmesini isteme hakkı verir. ForgeLM'in `forgelm purge` -> subcommand'ı, bir ForgeLM dağıtımının ürettiği veya tükettiği eğitim -> corpus'larına ve koşum-kapsamlı artefact'lara karşı bu talepleri -> karşılamak için operator-facing araçtır. +> subcommand'ı böyle bir talebi yanıtlamanın bir adımıdır: bir ForgeLM +> dağıtımının ürettiği veya tükettiği eğitim corpus'larından ve +> koşum-kapsamlı artefact'lardan satır kaldırır. Kişinin kimliğini doğrulamak, bir +> Madde 17(1)'deki nedenlerden birinin geçerli olup olmadığına karar vermek, Madde 12 +> süresi içinde yanıtlamak ve eğitilmiş ağırlıklarla ilgili her şey sizin +> sürecinizde kalır. ## `forgelm purge` neyi yapar (ve neyi yapmaz) **Yapar:** - JSONL eğitim corpus'undan tek bir satırı siler (`--row-id --corpus `). -- Bir koşumun staging dizinini veya compliance bundle'ını siler (`--run-id --kind {staging,artefacts}`). +- Bir koşumun staging dizinini ve adı run id'yi embed eden compliance dosyalarını siler (`--run-id --kind {staging,artefacts}`; `artefacts` standart paketi bulamaz, aşağıya bakın). - Yüklü config'in `retention:` block'una karşı retention-policy ihlallerini raporlar (`--check-policy`). - Her aksiyonu tamper-evident bir audit-event zinciriyle kaydeder — `request → completed` (veya `request → failed`) — böylece adli inceleyici tam olarak ne olduğunu yeniden inşa edebilir. -- Operatör tarafından sağlanan satır identifier'larını audit log'a girmeden önce SHA-256 ile hash'ler; böylece zincirin kendisi kişisel-veri sızıntısına dönüşmez (Madde 5(1)(c) veri minimizasyonu). +- Operatör tarafından sağlanan satır identifier'larını audit log'a girmeden önce per-output-dir salt ile hash'ler; böylece log identifier'ı açık halde tutmaz (Madde 5(1)(c) veri minimizasyonu). Digest anonim değil, kişisel veridir (Gerekçe 26); ek bilgi ayrı tutulup kendi kontrolleri altında korunuyorsa takma adlandırma (pseudonymisation, Madde 4(5)) sayılır. Varsayılan olarak `.forgelm_audit_salt` dosyası log'un yanında durur; ikisini de okuyabilen herkes tahmin ettiği bir identifier'dan digest'i yeniden hesaplayabilir. `FORGELM_AUDIT_SECRET` ayarlayın (salt'a XOR'lanır, dosya tek başına yetmez) ve log'u ile salt'ı kişisel veri gibi koruyun. **Yapmaz:** - Modelleri yeniden eğitmez. Bir satırı corpus'tan kaldırmak onu zaten eğitilmiş ağırlıklardan unutturmaz — silinmiş satır olmadan tam yeniden eğitim tek doğru çözüm. Tool, `--output-dir` altında doğrudan bir `final_model/` dizini mevcut olduğunda `data.erasure_warning_memorisation` yayar (`--output-dir` varsayılan olarak corpus'un dizinidir; bu yüzden eğitim çıktı dizininizi verin); böylece operatör boşluğu görür. Corpus'u hangi koşumların tükettiğini ayırt edemez. -- Audit log'un kendisinden entry silmez. Madde 17(3)(b) audit / muhasebe kayıtlarını bir hukuki yükümlülük savunması olarak korur; tool silmeyi geçmişi yeniden yazmak yerine yeni bir event olarak kaydeder. -- Downstream tüketicilere silme bildirimi göndermez. Webhook receiver'ları, dataset mirror'ları, deploy edilmiş model endpoint'leri ve yayınlanmış checkpoint'ten türetilmiş üçüncü-taraf fine-tune'lar operatörün runtime-katmanı sorumluluğudur (Madde 17(2)). Tool yüklü config webhook block'u içerdiğinde `data.erasure_warning_external_copies` yayar; böylece audit log'u sorgulayan downstream tüketici açık hatırlatmayı görür. +- Audit log'un kendisinden entry silmez. Tool, silmeyi geçmişi yeniden yazmak yerine yeni bir event olarak kaydeder ve audit log'dan hiçbir girdiyi silmez. Veri sahibini adlandıran girdilerin tutulup tutulamayacağı veri sorumlusunun kararıdır ve belgelenmelidir. Madde 17(1)'deki silme nedenlerinden biri geçerliyse girdiler yalnızca bir Madde 17(3) istisnasının kapsadığı yerde kalır: 17(3)(b), yalnızca işlemenin veri sorumlusunun tabi olduğu AB veya üye devlet hukukundan doğan bir yasal yükümlülüğe uyum için (veya kamu yararına ya da kamu otoritesinin kullanılmasına ilişkin bir görev için) gerekli olduğu ölçüde uygulanır; 17(3)(e) ise hukuki taleplerin ileri sürülmesini, kullanılmasını veya savunulmasını kapsar. Madde 17(1)'deki nedenlerin hiçbiri geçerli değilse (girdilerin toplandıkları amaç için hâlâ gerekli olması yalnızca 17(1)(a)'yı dışlar; rızanın geri çekilmesini, Madde 21 itirazını ve hukuka aykırı işlemeyi de kontrol edin) silme gerekmez; ancak talebe yine de gecikmeksizin ve en geç talebin alınmasından itibaren bir ay içinde yanıt verilir; yanıtta gerekçe, bir denetim otoritesine şikâyet etme ve yargı yoluna başvurma imkânı bildirilir (Madde 12(4)). +- Downstream tüketicilere silme bildirimi göndermez. Webhook receiver'ları, dataset mirror'ları, deploy edilmiş model endpoint'leri ve yayınlanmış checkpoint'ten türetilmiş üçüncü-taraf fine-tune'lar operatörün runtime-katmanı sorumluluğudur (GDPR Madde 19, verinin ifşa edildiği her alıcıya silmeyi bildirmeyi ister; bu, imkânsız olmadıkça veya orantısız çaba gerektirmedikçe geçerlidir ve veri sahibi isterse bu alıcıları ona da bildirmek gerekir; Madde 17(2) ise veri sorumlusu veriyi kamuya açtıysa makul adımlar ekler). Tool, row modunda `--config` ile verdiğiniz config webhook block'u içerdiğinde `data.erasure_warning_external_copies` yayar; böylece audit log'u sorgulayan downstream tüketici açık hatırlatmayı görür. - Yedekleri silmez. Operatörün `` sınırı dışındaki replikalar, snapshot'lar ve storage backup'lar altyapı tarafı endişelerdir. +- Yanıtın tamamını vermez. Eksiksiz bir yanıt, ForgeLM'in yapmadığı adımları da gerektirir: talep edenin kimliğini makul önlemlerle doğrulamak (Gerekçe 64; Madde 12(6), makul şüphe varsa ek bilgi istemenize izin verir); talebin alınmasından itibaren bir ay içinde yanıtlamak (Madde 12(3); karmaşık veya çok sayıda talep için iki ay daha uzatılabilir, uzatma ilk ay içinde bildirilir); bir erişim talebinde, veri sahibine verinin bir kopyasını ve Madde 15(1)(a)-(h) bilgilerini vermek (Madde 15(3)) ve veri üçüncü bir ülkeye aktarıldıysa Madde 46 güvencelerini (Madde 15(2)) vermek; bunu başkalarının hak ve özgürlüklerini olumsuz etkilemeden yapmak (Madde 15(4)); bir silme talebinde, Madde 17(1)'deki nedenlerden birinin geçerli olup olmadığına ve tuttuğunuz şeyi hangi Madde 17(3) istisnasının (varsa) kapsadığına karar vermek; alıcıları bilgilendirmek (Madde 19); satırların gittiğini doğrulamak için purge sonrasında erişim aramasını yeniden çalıştırmak; ve bir talebi reddederseniz gecikmeksizin ve en geç bir ay içinde gerekçeyi, şikâyet etme ve yargı yoluna başvurma imkânını bildirmek (Madde 12(4)). Eğitilmiş modelin kişisel veri sayılıp sayılmayacağı ayrı bir sorudur (EDPB Görüş 28/2024). ## Üç mod @@ -38,11 +42,11 @@ $ forgelm purge \ Tool şunları yapar: -1. Per-output-dir salt'ı `/.forgelm_audit_salt`'ta çözer (ilk kullanımda oluşturur; mode `0600`; `FORGELM_AUDIT_SECRET[:16]` set'liyse XOR'lar). **Not:** bu XOR yalnızca **identifier hashing**'i besler — audit-chain HMAC anahtarı bağımsız olarak `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` şeklinde türetilir (bkz. [`docs/qms/access_control.md`](../qms/access_control-tr.md) §3.4 / `forgelm/compliance.py:104-114`). İki primitif kasıtlı olarak ayrıdır. +1. Per-output-dir salt'ı `/.forgelm_audit_salt`'ta çözer (ilk kullanımda oluşturur; mode `0600`; `FORGELM_AUDIT_SECRET[:16]` set'liyse XOR'lar). **Not:** bu XOR yalnızca **identifier hashing**'i besler — audit-chain HMAC anahtarı bağımsız olarak `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` şeklinde türetilir (bkz. [`docs/qms/access_control.md`](../qms/access_control-tr.md) §3.4 / `forgelm/compliance.py` içindeki `AuditLogger.__init__`). İki primitif kasıtlı olarak ayrıdır. 2. Audit log'a `data.erasure_requested` yazar — **hash'lenmiş** target_id (`SHA-256(salt + value)`) ve operatör-sağlanan justification ile. -3. Eşleşen JSONL satırını `id` (veya `row_id`) field'ı ile bulur — re-order edilmiş bir dosyada sessiz yanlış-satır silmesini engellemek için satır-numarası fallback'i **reddedilir**. ForgeLM şu an bir id-doldurma yardımcısı sunmuyor (Phase 28 backlog'unda `forgelm audit --add-row-ids` flag'i bekliyor); id'siz corpus'lara sahip operatörler purge'den önce id'leri operatör-tarafı bir script ile (örn. `jq -c 'to_entries | with_entries(...)'` ya da tek-seferlik bir Python döngüsü) pre-populate etmeli. +3. Eşleşen JSONL satırını `id` (veya `row_id`) field'ı ile bulur — re-order edilmiş bir dosyada sessiz yanlış-satır silmesini engellemek için satır-numarası fallback'i **reddedilir**. ForgeLM şu an bir id-doldurma yardımcısı sunmuyor (Phase 28 backlog'unda `forgelm audit --add-row-ids` flag'i bekliyor); id'siz corpus'lara sahip operatörler purge'den önce id'leri operatör-tarafı bir script ile pre-populate etmeli (GDPR / KVKK kullanıcı kılavuzu sayfası, id'si olmayan her satıra bir UUID veren ve yinelenen id'leri reddeden test edilmiş bir Python script'i içerir). 4. Corpus'u atomic olarak yeniden yazar (kardeş bir temp dosyaya yazar + `os.replace`); operatörler ya tam silme-öncesi dosyayı ya da tam silme-sonrası dosyayı görür, asla kısmi state'i değil. -5. Uyarı koşullarını tespit eder ve eşleşen event'leri yayar (memorization, synthetic-data varlığı, dış kopyalar). +5. Uyarı koşullarını tespit eder ve eşleşen event'leri yayar (memorization, synthetic-data varlığı, dış kopyalar). Dış-kopya kontrolü `--config` ile verdiğiniz config'in `webhook` bloğunu okur (literal bir `url` ya da purge ortamında export edilmiş bir `url_env` değişkeni); `--config` olmadan ya da `url_env` ayarsızsa tetiklenemez. 6. Disk operasyonu raise ederse `data.erasure_failed`, başarılıysa `data.erasure_completed` yazar; zincir nihai state'i yansıtır. Çoklu-satır eşleşmeleri varsayılan olarak reddedilir (`--row-matches one`); id'yi paylaşan her satırı silmek için `--row-matches all` geç (operatör niyeti onaylar). @@ -56,9 +60,9 @@ $ forgelm purge --run-id fg-abc123def456 --kind staging --output-dir ./outputs $ forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs ``` -- `--kind staging` — `/final_model.staging./`'i siler (mevcutsa legacy `final_model.staging/` da). -- `--kind artefacts` — ``'i adında embed eden `/compliance/*.json` dosyalarını siler. -- `--kind logs` **bilinçli olarak yok**: audit log'lar append-only Madde 17(3)(b) kayıtları; tool tarafından silinmez. +- `--kind staging` — `/final_model.staging./`'i siler; legacy `final_model.staging/`'i yalnızca içeriği ``'ye eşit bir `staging_run_id` işaretçisi taşıyorsa siler (ForgeLM bu işaretçiyi yazmaz; aksi halde legacy dizini kendiniz silin). +- `--kind artefacts` — `/compliance/` altında doğrudan bulunan, adı ``'yi `_`, `-`, `.` ya da adın başı/sonu ile sınırlı olarak içeren her dosyayı siler (yalnızca `.json` değil). **Standart paketi bulamaz:** trainer run id içermeyen sabit dosya adları yazar (`compliance_report.json`, `training_manifest.yaml`, ...), bu yüzden normal bir çıktı dizininde komut hiçbir şey bulmaz, `data.erasure_failed` (`NoMatchingArtefacts`) yayar, 1 ile çıkar ve hiçbir şey silmez. Bu, gerçek bir run id için (`fg-…`) geçerlidir: sabit bir adda geçen genel bir token (örneğin `json` veya `report`) o dosyalarla eşleşir ve onları siler; bu yüzden her zaman tam run id'yi verin. Resolver düzeltilene kadar paketi depolama katmanınız üzerinden kaldırın. +- `--kind logs` **bilinçli olarak yok**: audit log append-only'dir ve tool onu asla silmez (girdilerin tutulup tutulamayacağına kimin karar verdiği için yukarıdaki "Yapmaz" listesine bakın). ### 3. Retention policy raporu @@ -66,22 +70,22 @@ $ forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs $ forgelm purge --check-policy --config configs/run.yaml --output-dir ./outputs ``` -Salt-okunur tarama. Output dizinini gezer, her artefact'ın yaşını (canonical: audit-log genesis `timestamp`'i; fallback: filesystem `mtime` + `age_source=mtime` flag'i ile) yüklü config'in `retention:` horizon'larına karşı kıyaslar ve structured violation list yayar. +Salt-okunur tarama. Output dizinini gezer, her artefact'ın yaşını (canonical: `final_model.staging./` dizini için koşumun ilk audit event'i, yoksa audit-log genesis `timestamp`'i; fallback: filesystem `mtime` + `age_source=mtime` flag'i ile) yüklü config'in `retention:` horizon'larına karşı kıyaslar ve structured violation list yayar. -**Başarılı policy raporları 0 ile çıkar** (rapor-değil-gate semantiği — design §10 Q5). Config-load başarısızlıkları `EXIT_CONFIG_ERROR` (sıfır olmayan) ile çıkar: eksik / okunamayan / Pydantic validation'ı geçemeyen explicit `--config`, "loader failed" durumunu "ihlal yok" sanmamak için `EXIT_CONFIG_ERROR` olarak yüzeye çıkar. CI gate isteyen operatörler `--output-format json` kullanır ve `jq '.violations | length'`'e pipe'lar; bu, public exit-code kontratını `0/1/2/3/4` her ForgeLM subcommand'ında tutarlı tutar. +**Başarılı policy raporları 0 ile çıkar** (rapor-değil-gate semantiği — design §10 Q5). Config-load başarısızlıkları `EXIT_CONFIG_ERROR` (sıfır olmayan) ile çıkar: eksik / okunamayan / Pydantic validation'ı geçemeyen explicit `--config`, "loader failed" durumunu "ihlal yok" sanmamak için `EXIT_CONFIG_ERROR` olarak yüzeye çıkar. CI gate isteyen operatörler `--output-format json` kullanır ve raporu kendileri sınar, örneğin `set -o pipefail` altında `jq -e '.success == true and has("count") and .count == 0'`: `count`, hiçbir `retention:` bloğu yüklenmediğinde yoktur; böylece gate hiç kontrol edilmemiş bir dizinde geçmek yerine başarısız olur, ve `--output-dir` gerçek eğitim çıktı dizini olmalıdır (olmayan bir dizin `count: 0` raporlar). Bu, public exit-code kontratını her ForgeLM subcommand'ında tutarlı tutar. ## `retention:` config block'u ```yaml retention: - audit_log_retention_days: 1825 # 5 yıl (Madde 12 kayıt-tutma) - staging_ttl_days: 7 # `forgelm reject` üzerine aksiyon almak için bir iş haftası - ephemeral_artefact_retention_days: 90 # üç-aylık review cadence'i - raw_documents_retention_days: 90 # data audit yeniden çalıştırmadan önce ingestion penceresi - enforce: log_only # ayrılmış: henüz hiçbir komut onu kullanmaz (log_only / warn_on_excess / block_on_excess) + audit_log_retention_days: 1825 # 5 yıl (ForgeLM varsayılanı, yasal süre değildir) + staging_ttl_days: 7 # purge --check-policy'nin staging dizinini süresi geçmiş saydığı yaş + ephemeral_artefact_retention_days: 90 # compliance/ ve data_audit_report.json, audit log'un ilk olayından itibaren yaşlandırılır + raw_documents_retention_days: 90 # yalnızca /raw_documents/ ve ingestion_output/ (ForgeLM bunları oluşturmaz) + enforce: log_only # ayrılmış: henüz hiçbir komut onu dikkate almaz (log_only / warn_on_excess / block_on_excess) ``` -Herhangi bir horizon'u `0` olarak ayarlamak o artefact kind'ı için policy'yi devre dışı bırakır (sınırsız tut). Horizon'lar `forgelm purge --check-policy` ile raporlanır (salt-okunur, her zaman 0 ile çıkar); hiçbir şey otomatik silinmez ya da engellenmez. `enforce` ayrılmıştır: henüz hiçbir komut onu kullanmaz, bu yüzden üç değer de `log_only` gibi davranır. +Herhangi bir horizon'u `0` olarak ayarlamak o artefact kind'ı için raporu kapatır (her iki durumda da hiçbir şey silinmez). Horizon'lar `forgelm purge --check-policy` ile raporlanır (salt-okunur; kaç tane bulursa bulsun 0 ile çıkar); hiçbir şey otomatik silinmez ya da engellenmez. ForgeLM `raw_documents/` veya `ingestion_output/` dizinlerini oluşturmaz; `raw_documents_retention_days` yalnızca kaynak dokümanları orada tutuyorsanız etkili olur. `enforce` ayrılmıştır: henüz hiçbir komut onu dikkate almaz, bu yüzden üç değer de `log_only` gibi davranır. ## Kaldırıldı: `evaluation.staging_ttl_days` @@ -98,7 +102,7 @@ Herhangi bir horizon'u `0` olarak ayarlamak o artefact kind'ı için policy'yi d | `data.erasure_failed` | Disk operasyonu raise etti VEYA eşleşen satır/koşum bulunamadı VEYA çoklu-satır policy belirsizliği reddetti | Tüm `requested` field'ları + `error_class`, `error_message` | | `data.erasure_warning_memorisation` | **Yalnız row modunda.** Row erasure × `--output-dir` altında doğrudan bir `final_model/` dizini mevcut. (Run modu memorisation uyarısı emit etmez — run-scoped `staging` / `artefacts` türleri tanım gereği promote-öncesi.) | Tüm `completed` field'ları + `affected_run_ids` | | `data.erasure_warning_synthetic_data_present` | **Yalnız row modunda.** Row erasure × `output_dir`'de `synthetic_data*.jsonl` mevcut. (Run modu bu uyarıyı yüzeylemez — synthetic veri corpus-scoped'dur, run-scoped değil.) | Tüm `completed` field'ları + `synthetic_files` | -| `data.erasure_warning_external_copies` | Yüklü config webhook block'u içeriyor | Tüm `completed` field'ları + `webhook_targets` | +| `data.erasure_warning_external_copies` | **Yalnız row modunda, `--config` verildiğinde.** Yüklü config webhook block'u içeriyor. (Run modu `--config` yüklemez.) | Tüm `completed` field'ları + `webhook_targets` | ## Silme sonrası zincir doğrulama @@ -109,8 +113,8 @@ Herhangi bir horizon'u `0` olarak ayarlamak o artefact kind'ı için policy'yi d | Kod | Anlamı | |---|---| | 0 | Başarı veya başarılı `--check-policy` raporu (rapor-değil-gate semantiği). | -| 1 | Config hatası: bilinmeyen `--row-id`, eksik `--corpus`, mutually-exclusive flag kombinasyonu, çelişen `staging_ttl_days` değerleri veya eksik / okunamayan / Pydantic validation'ı geçemeyen `--check-policy --config `. | -| 2 | Runtime hatası: I/O failure, permission denied, atomic-rename failure. | +| 1 | Config hatası: bilinmeyen `--row-id`, `--run-id` ile eşleşen staging dizini / compliance dosyası yok (standart sabit-adlı pakete karşı `--kind artefacts` dahil), eksik `--corpus` ya da `--corpus`'suz `--row-id`, eksik `--kind`, seçilmemiş mod veya eksik / okunamayan / Pydantic validation'ı geçemeyen `--check-policy --config `. | +| 2 | Runtime hatası: I/O failure, permission denied, atomic-rename failure; ayrıca argüman-parser kullanım hatası (`--row-id` ile `--run-id` gibi birlikte verilen iki mod, `--kind logs`), JSON zarfı yerine usage basar. | `--check-policy` asla 3 kodu döndürmez. Başarılı bir rapor 0 ile çıkar; verilen `--config`'i yükleme başarısızlığı ise yanıltıcı bir "ihlal yok" raporuna düşmek yerine `EXIT_CONFIG_ERROR` (sıfır olmayan) ile çıkar. CI gate isteyen operatörler ihlal sayısını JSON çıktıdan kendileri hesaplar (design §10 Q5). @@ -143,9 +147,18 @@ Audit chain `data.access_request_query`'yi identifier *salt'lı hash'lenmiş* olarak kaydeder — `forgelm purge`'ün `target_id` field'ı için kullandığı aynı per-output-dir salt'ı yeniden kullanılır. Madde 15 erişim talepleri kendisi de subject'in verisini audit log'a -sızdırmamalıdır. Salt'lı form, compliance reviewer'ın aynı subject +sızdırmamalıdır. Salt'lı form, compliance reviewer'ın aynı veri sahibi için Madde 17 (purge) ve Madde 15 (reverse-pii) olaylarını cleartext -görmeden ilişkilendirmesini sağlar (digest'ler eşleşir). +görmeden ilişkilendirmesini sağlar. Digest'ler yalnızca `--row-id` ve +`--query` değerleri aynı metinse, iki komut da aynı `--output-dir`'i +kullanıyorsa ve `FORGELM_AUDIT_SECRET` ikisinde de aynı durumdaysa (ayarsız ya +da aynı değer) eşleşir; erişim talebini varsayılan düz-metin modunda çalıştırın: +`--salt-source` digest'lerin eşleşmesi için gerekmez; onunla hash-mask taraması +corpus içinde digest arar, bu yüzden düz metin içeren bir corpus'ta kişi orada +olsa bile sıfır eşleşme alırsınız. +Id'ler farklıysa erişim talebini ilgili silme işlemine, +`forgelm purge --justification`'a verdiğiniz ticket referansı üzerinden +bağlayın. **Identifier tipleri** (`--type`): `literal` (varsayılan), `email`, `phone`, `tr_id`, `us_ssn`, `iban`, `credit_card`, `custom`. @@ -176,6 +189,6 @@ glob); `2` = runtime hatası (mid-scan I/O failure). JSON envelope ## Bkz. -- `docs/qms/sop_data_management.md` — retention + erasure prosedürleri dahil tam data-lifecycle SOP. +- `docs/qms/sop_data_management.md` — veri yaşam döngüsü SOP'si (toplama, kalite, saklama); bir saklama ya da silme prosedürü içermez, bunu bu kılavuzdan yola çıkarak yazın. - `docs/usermanuals/tr/compliance/overview.md` — buraya link veren operator-facing compliance overview. - [`docs/design/gdpr_erasure.md`](../design/gdpr_erasure.md) — bu implementation'ın gerçekleştirdiği Phase 20 design dokümanı. diff --git a/docs/guides/gdpr_erasure.md b/docs/guides/gdpr_erasure.md index fd4f7e21..3e6842e6 100644 --- a/docs/guides/gdpr_erasure.md +++ b/docs/guides/gdpr_erasure.md @@ -3,26 +3,29 @@ > **Article 17 of the EU General Data Protection Regulation** (the > "right to be forgotten") gives data subjects the right to request > erasure of personal data a controller holds about them. ForgeLM's -> `forgelm purge` subcommand is the operator-facing tool for honouring -> those requests against training corpora and run-scoped artefacts a -> ForgeLM deployment produced or consumed. +> `forgelm purge` subcommand is one step of answering such a request: it +> removes rows from the training corpora and run-scoped artefacts a ForgeLM +> deployment produced or consumed. Identifying the person, deciding whether +> an Article 17(1) ground applies, answering within the Article 12 deadline +> and everything about the trained weights remain your process. ## What `forgelm purge` does (and does not) **Does:** - Erases a single row from a JSONL training corpus (`--row-id --corpus `). -- Deletes a run's staging directory or compliance bundle (`--run-id --kind {staging,artefacts}`). +- Deletes a run's staging directory, and compliance files whose names embed the run id (`--run-id --kind {staging,artefacts}`; `artefacts` does not find the standard bundle, see below). - Reports retention-policy violations against the loaded config's `retention:` block (`--check-policy`). - Records every action with a tamper-evident audit-event chain — `request → completed` (or `request → failed`) — so a forensic reviewer can reconstruct exactly what happened. -- Hashes operator-supplied row identifiers with SHA-256 before they enter the audit log so the chain itself does not become a personal-data leak (Article 5(1)(c) data minimisation). +- Hashes operator-supplied row identifiers with a per-output-dir salt before they enter the audit log, so the log does not hold the identifier in clear (Article 5(1)(c) data minimisation). The digest is personal data, not anonymous data (recital 26), and it counts as pseudonymisation (Article 4(5)) only if the additional information is kept separately under its own controls. By default the `.forgelm_audit_salt` file sits next to the log, so anyone who can read both can recompute a digest from a guessed identifier; set `FORGELM_AUDIT_SECRET` (it is XOR-ed into the salt, so the file alone is no longer enough) and protect the log and the salt like personal data. **Does not:** - Re-train models. Removing a row from the corpus does not unmemorise it from already-trained weights — full retraining without the erased row is the only proper mitigation. The tool emits `data.erasure_warning_memorisation` when a `final_model/` directory exists directly under `--output-dir` (which defaults to the corpus's directory, so pass your training output directory), so the operator sees the gap. It cannot tell which runs consumed the corpus. -- Delete entries from the audit log itself. Article 17(3)(b) preserves audit / accounting records as a legal-obligation defence; the tool records the erasure as a new event rather than rewriting history. -- Push erasure notices to downstream consumers. Webhook receivers, dataset mirrors, deployed model endpoints, and third-party fine-tunes derived from a published checkpoint are the operator's runtime-layer responsibility (Article 17(2)). The tool emits `data.erasure_warning_external_copies` when the loaded config has a webhook block, so a downstream consumer querying the audit log sees the explicit reminder. +- Delete entries from the audit log itself. The tool records the erasure as a new event rather than rewriting history, and never deletes an audit-log entry. Whether entries that name the data subject may be kept is the controller's decision, and it has to be documented. If an Article 17(1) ground applies, the entries stay only where an Article 17(3) exception covers them: 17(3)(b) applies only to the extent processing is necessary for compliance with a legal obligation under Union or Member State law to which the controller is subject (or for a task in the public interest or in the exercise of official authority), and 17(3)(e) covers the establishment, exercise or defence of legal claims. If no Article 17(1) ground applies (the entries still being necessary for the purpose they were collected for rules out only 17(1)(a); also check consent withdrawal, an Article 21 objection and unlawful processing), erasure is not owed, but the request still gets an answer without delay and at the latest within one month of receipt, giving the reasons and the possibility of complaining to a supervisory authority and seeking a judicial remedy (Article 12(4)). +- Push erasure notices to downstream consumers. Webhook receivers, dataset mirrors, deployed model endpoints, and third-party fine-tunes derived from a published checkpoint are the operator's runtime-layer responsibility (GDPR Article 19 requires telling each recipient the data was disclosed to about the erasure, unless that proves impossible or involves disproportionate effort, and telling the data subject about those recipients on request; Article 17(2) adds reasonable steps where the controller made the data public). The tool emits `data.erasure_warning_external_copies` in row mode when the config you pass with `--config` has a webhook block, so a downstream consumer querying the audit log sees the explicit reminder. - Erase backups. Replicas, snapshots, and storage backups outside the operator's `` are infrastructure-side concerns. +- Make the whole response. A complete response also needs steps ForgeLM does not perform: verifying the requester's identity with reasonable measures (recital 64; Article 12(6) lets you ask for more information where you have reasonable doubts); answering within one month of receipt (Article 12(3); extendable by two further months for complex or numerous requests, with notice in the first month); for an access request, giving the data subject a copy of the data and the Article 15(1)(a)-(h) information (Article 15(3)) and, where the data went to a third country, the Article 46 safeguards (Article 15(2)), without adversely affecting other people's rights (Article 15(4)); for an erasure request, deciding whether an Article 17(1) ground applies and which Article 17(3) exception, if any, covers what you keep; telling recipients (Article 19); re-running the access search after a purge to confirm the rows are gone; and, if you refuse a request, informing the requester without delay and at the latest within one month of the reasons, the possibility of complaining and the possibility of seeking a judicial remedy (Article 12(4)). Whether the trained model counts as personal data is a separate question (EDPB Opinion 28/2024). ## Three modes @@ -38,11 +41,11 @@ $ forgelm purge \ The tool: -1. Resolves the per-output-dir salt at `/.forgelm_audit_salt` (creates it on first use; mode `0600`; XOR'd with `FORGELM_AUDIT_SECRET[:16]` if the env var is set). **Note:** this XOR feeds **identifier hashing only** — the audit-chain HMAC key is derived independently as `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` (see [`docs/qms/access_control.md`](../qms/access_control.md) §3.4 / `forgelm/compliance.py:104-114`). The two primitives are intentionally separate. +1. Resolves the per-output-dir salt at `/.forgelm_audit_salt` (creates it on first use; mode `0600`; XOR'd with `FORGELM_AUDIT_SECRET[:16]` if the env var is set). **Note:** this XOR feeds **identifier hashing only** — the audit-chain HMAC key is derived independently as `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` (see [`docs/qms/access_control.md`](../qms/access_control.md) §3.4 / `AuditLogger.__init__` in `forgelm/compliance.py`). The two primitives are intentionally separate. 2. Writes `data.erasure_requested` to the audit log with the **hashed** target_id (`SHA-256(salt + value)`) and the operator-supplied justification. -3. Locates the matching JSONL row by its `id` (or `row_id`) field — line-number fallback is **rejected** to defend against silent wrong-row deletion on a re-ordered file. ForgeLM does not currently provide an id-population helper (a `forgelm audit --add-row-ids` flag is on the Phase 28 backlog); operators with id-less corpora must pre-populate ids with an operator-side script (e.g. `jq -c 'to_entries | with_entries(...)'` or a one-shot Python loop) before invoking purge. +3. Locates the matching JSONL row by its `id` (or `row_id`) field — line-number fallback is **rejected** to defend against silent wrong-row deletion on a re-ordered file. ForgeLM does not currently provide an id-population helper (a `forgelm audit --add-row-ids` flag is on the Phase 28 backlog); operators with id-less corpora must pre-populate ids with an operator-side script before invoking purge (the GDPR / KVKK user-manual page carries a tested Python script that gives every row without one a UUID and refuses duplicate ids). 4. Atomically rewrites the corpus (writes a sibling temp file + `os.replace`); operators get either the full pre-erasure file or the full post-erasure file, never a partial state. -5. Detects warning conditions and emits the matching events (memorisation, synthetic-data presence, external copies). +5. Detects warning conditions and emits the matching events (memorisation, synthetic-data presence, external copies). The external-copies check reads the `webhook` block of the config you pass with `--config` (a literal `url`, or a `url_env` variable that is exported in the purge environment); without `--config`, or with an unset `url_env`, it cannot fire. 6. Writes `data.erasure_completed` (or `data.erasure_failed` if the disk operation raised) so the chain reflects the final state. Multi-row matches default to refusal (`--row-matches one`); pass `--row-matches all` to delete every row sharing the id (operator confirms intent). @@ -56,9 +59,9 @@ $ forgelm purge --run-id fg-abc123def456 --kind staging --output-dir ./outputs $ forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs ``` -- `--kind staging` removes `/final_model.staging./` (and the legacy `final_model.staging/` if present). -- `--kind artefacts` removes `/compliance/*.json` files whose name embeds the ``. -- `--kind logs` is **intentionally absent**: audit logs are append-only Article 17(3)(b) records and are not deleted by the tool. +- `--kind staging` removes `/final_model.staging./`, and the legacy `final_model.staging/` only if it carries a `staging_run_id` marker whose content equals the `` (ForgeLM does not write that marker; otherwise remove the legacy directory yourself). +- `--kind artefacts` removes any file directly under `/compliance/` whose name contains the `` bounded by `_`, `-`, `.` or the start/end of the name (not only `.json`). **It does not find the standard bundle:** the trainer writes fixed basenames (`compliance_report.json`, `training_manifest.yaml`, ...) that contain no run id, so on a normal output directory the command finds nothing, emits `data.erasure_failed` (`NoMatchingArtefacts`), exits 1 and deletes nothing. That holds for a real run id (`fg-…`): a generic token that appears in a fixed name, such as `json` or `report`, does match those files and deletes them, so always pass the full run id. Remove the bundle through your storage layer until the resolver is fixed. +- `--kind logs` is **intentionally absent**: the audit log is append-only and the tool never deletes it (see the "Does not" list above for who decides whether its entries may be kept). ### 3. Retention-policy report @@ -66,22 +69,22 @@ $ forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs $ forgelm purge --check-policy --config configs/run.yaml --output-dir ./outputs ``` -Read-only scan. Walks the output directory, compares each artefact's age (canonical: audit-log genesis `timestamp`; fallback: filesystem `mtime` with `age_source=mtime` flag) against the loaded config's `retention:` horizons, and emits a structured violation list. +Read-only scan. Walks the output directory, compares each artefact's age (canonical: the run's first audit event for a `final_model.staging./` directory, else the audit log's genesis `timestamp`; fallback: filesystem `mtime` with `age_source=mtime` flag) against the loaded config's `retention:` horizons, and emits a structured violation list. -**Successful policy reports exit 0** (report-not-gate semantic per design §10 Q5). Config-load failures exit with `EXIT_CONFIG_ERROR` (non-zero): an explicit `--config` that is missing, unreadable, or fails Pydantic validation surfaces as `EXIT_CONFIG_ERROR` so the operator does not mistake "loader failed" for "no violations". Operators wiring a CI gate use `--output-format json` and pipe to `jq '.violations | length'` themselves; this keeps the public exit-code contract `0/1/2/3/4` consistent across every ForgeLM subcommand. +**Successful policy reports exit 0** (report-not-gate semantic per design §10 Q5). Config-load failures exit with `EXIT_CONFIG_ERROR` (non-zero): an explicit `--config` that is missing, unreadable, or fails Pydantic validation surfaces as `EXIT_CONFIG_ERROR` so the operator does not mistake "loader failed" for "no violations". Operators wiring a CI gate use `--output-format json` and test the report themselves, for example `jq -e '.success == true and has("count") and .count == 0'` under `set -o pipefail`: `count` is absent when no `retention:` block was loaded, so the gate fails instead of passing on a directory that was never checked, and `--output-dir` must be the real training output directory (a missing one reports `count: 0`). This keeps the public exit-code contract consistent across every ForgeLM subcommand. ## The `retention:` config block ```yaml retention: - audit_log_retention_days: 1825 # 5 years (Article 12 record-keeping) - staging_ttl_days: 7 # one work-week to act on a `forgelm reject` - ephemeral_artefact_retention_days: 90 # quarterly review cadence - raw_documents_retention_days: 90 # ingestion-window before re-running data audit + audit_log_retention_days: 1825 # 5 years (ForgeLM default, not a legal period) + staging_ttl_days: 7 # age at which `purge --check-policy` reports a staging dir as overdue + ephemeral_artefact_retention_days: 90 # compliance/ and data_audit_report.json, aged from the audit log's first event + raw_documents_retention_days: 90 # only /raw_documents/ and ingestion_output/ (ForgeLM does not create them) enforce: log_only # reserved: no command acts on it yet (log_only / warn_on_excess / block_on_excess) ``` -Setting any horizon to `0` disables the policy for that artefact kind (retain indefinitely). The horizons are reported by `forgelm purge --check-policy` (read-only, always exits 0); nothing is deleted or blocked automatically. `enforce` is reserved: no command acts on it yet, so all three values behave like `log_only`. +Setting any horizon to `0` turns the report off for that artefact kind (nothing is deleted either way). The horizons are reported by `forgelm purge --check-policy` (read-only; exits 0 however many it finds); nothing is deleted or blocked automatically. ForgeLM does not create `raw_documents/` or `ingestion_output/`; `raw_documents_retention_days` has an effect only if you keep source documents there. `enforce` is reserved: no command acts on it yet, so all three values behave like `log_only`. ## Removed: `evaluation.staging_ttl_days` @@ -98,7 +101,7 @@ Six new events ship with `forgelm purge` (catalogued in `docs/reference/audit_ev | `data.erasure_failed` | Disk operation raised, OR no matching row/run found, OR multi-row policy refused on ambiguity | All `requested` fields + `error_class`, `error_message` | | `data.erasure_warning_memorisation` | **Row mode only.** Row erasure × a `final_model/` directory exists directly under `--output-dir`. (Run mode does not emit memorisation warnings — the run-scoped `staging` / `artefacts` kinds are by definition pre-promotion.) | All `completed` fields + `affected_run_ids` | | `data.erasure_warning_synthetic_data_present` | **Row mode only.** Row erasure × `synthetic_data*.jsonl` exists in `output_dir`. (Run mode does not surface this warning — synthetic data is corpus-scoped, not run-scoped.) | All `completed` fields + `synthetic_files` | -| `data.erasure_warning_external_copies` | Loaded config has a webhook block (both modes). | All `completed` fields + `webhook_targets` | +| `data.erasure_warning_external_copies` | **Row mode only, when `--config` is given.** Loaded config has a webhook block. (Run mode never loads `--config`.) | All `completed` fields + `webhook_targets` | ## Verifying a chain post-erasure @@ -109,8 +112,8 @@ Six new events ship with `forgelm purge` (catalogued in `docs/reference/audit_ev | Code | Meaning | |---|---| | 0 | Success, or a successful `--check-policy` report (report-not-gate semantic). | -| 1 | Config error: unknown `--row-id`, missing `--corpus`, mutually-exclusive flag combination, conflicting `staging_ttl_days` values, or `--check-policy --config ` that is missing / unreadable / fails Pydantic validation. | -| 2 | Runtime error: I/O failure, permission denied, atomic-rename failure. | +| 1 | Config error: unknown `--row-id`, no staging directory / compliance file matching `--run-id` (including `--kind artefacts` against the standard fixed-name bundle), a missing `--corpus` or `--row-id` without `--corpus`, a missing `--kind`, no mode selected, or `--check-policy --config ` that is missing / unreadable / fails Pydantic validation. | +| 2 | Runtime error: I/O failure, permission denied, atomic-rename failure; also an argument-parser usage error (two modes together such as `--row-id` with `--run-id`, `--kind logs`), which prints usage instead of a JSON envelope. | `--check-policy` never returns code 3. A successful report exits 0; a failure to load the supplied `--config` exits with `EXIT_CONFIG_ERROR` (non-zero) rather than degrading to a misleading "no violations" report. Operators wiring a CI gate compute the violation count from JSON output themselves (per design §10 Q5). @@ -143,8 +146,16 @@ that `forgelm purge` uses for its `target_id` field — Article 15 access requests must not themselves leak the subject's data into the audit log. The salted form is a stable per-identifier fingerprint that lets a compliance reviewer correlate Article 17 (purge) and -Article 15 (reverse-pii) events for the same subject (the digests -match) without seeing the cleartext. +Article 15 (reverse-pii) events for the same subject without seeing the +cleartext. The digests match only when the `--row-id` and `--query` values +are the same text, both commands use the same `--output-dir`, and +`FORGELM_AUDIT_SECRET` is in the same state for both (unset, or the same +value); run the access request in its default plaintext mode: `--salt-source` is +not needed for the digests to match, and with it the hash-mask scan looks +for digests inside the corpus, so a plaintext corpus reports zero matches +even when the person is there. When +the ids differ, link a request to its erasure +through the ticket reference you pass to `forgelm purge --justification`. **Identifier types** (`--type`): `literal` (default), `email`, `phone`, `tr_id`, `us_ssn`, `iban`, `credit_card`, `custom`. All @@ -175,6 +186,6 @@ for the JSON envelope schema. ## See also -- `docs/qms/sop_data_management.md` — the full data-lifecycle SOP including the retention + erasure procedures. +- `docs/qms/sop_data_management.md` — the data-lifecycle SOP (collection, quality, storage); it does not contain a retention or erasure procedure, so write one from this guide. - `docs/usermanuals/en/compliance/overview.md` — the operator-facing compliance overview that links here. - [`docs/design/gdpr_erasure.md`](../design/gdpr_erasure.md) — the Phase 20 design document that this implementation realises. diff --git a/docs/guides/ingestion-tr.md b/docs/guides/ingestion-tr.md index fc73b77a..d7dea17d 100644 --- a/docs/guides/ingestion-tr.md +++ b/docs/guides/ingestion-tr.md @@ -102,12 +102,13 @@ US SSN) chunk'lar JSONL'a inmeden önce redact etmek için `--pii-mask` ekleyin: ```bash -forgelm ingest ./customer_emails/ --output data/anon.jsonl --pii-mask +forgelm ingest ./customer_emails/ --output data/masked.jsonl --pii-mask ``` Tespit edilen span'ler `[REDACTED]` ile değiştirilir. Tespit regex -tabanlıdır — false positive'ler kasıtlıdır. Sonrasında -`forgelm audit` ile çıktıyı doğrulayın. +tabanlıdır — false positive'ler kasıtlıdır. Sonrasında çıktıyı +`forgelm audit` ile denetleyin: aynı regex'leri yeniden uygular, yani o pattern'lerin artık eşleşmediğini doğrular; +`--pii-ml` isim ve konum sayımı ekler. Maskelenmiş metin, geriye kalan bilgilerden kişinin kimliği belirlenebildiği sürece hâlâ kişisel veridir. --- diff --git a/docs/guides/ingestion.md b/docs/guides/ingestion.md index 708ad92b..e174cc88 100644 --- a/docs/guides/ingestion.md +++ b/docs/guides/ingestion.md @@ -101,12 +101,13 @@ IBAN, and national IDs (TR Kimlik No, DE Personalausweis, FR INSEE, US SSN) before any chunk lands in the JSONL: ```bash -forgelm ingest ./customer_emails/ --output data/anon.jsonl --pii-mask +forgelm ingest ./customer_emails/ --output data/masked.jsonl --pii-mask ``` Detected spans are replaced with `[REDACTED]`. Detection is regex-based — false positives are intentional. Audit your output afterwards with -`forgelm audit` to verify. +`forgelm audit`: it re-applies the same regexes, so it confirms those patterns no longer match, and +`--pii-ml` adds a count of names and locations. Masked text is still personal data wherever a person can be re-identified from what remains. --- diff --git a/docs/guides/iso_soc2_deployer_guide-tr.md b/docs/guides/iso_soc2_deployer_guide-tr.md index 196d0689..9ee66a10 100644 --- a/docs/guides/iso_soc2_deployer_guide-tr.md +++ b/docs/guides/iso_soc2_deployer_guide-tr.md @@ -281,8 +281,9 @@ forgelm reverse-pii --query "alice@example.com" --type email \ --output-dir ./outputs data/*.jsonl # JSON envelope { matches: [...], match_count: N } döner # Audit chain QUERY HASH'LENMİŞ ŞEKİLDE bir -# `data.access_request_query` event damgalar (asla raw — salt audit -# log'un kendisine karşı wordlist saldırılarına karşı koruma sağlar). +# `data.access_request_query` event damgalar (asla raw). Salt'lı digest +# kişisel veridir, anonim değildir: `.forgelm_audit_salt`'ı log'dan ayrı +# tutun (veya FORGELM_AUDIT_SECRET ayarlayın), yoksa tahmin edilen bir identifier ona karşı sınanabilir. ``` Madde 17 (silme hakkı): @@ -292,15 +293,17 @@ forgelm purge --row-id "alice@example.com" --corpus data/2026Q2.jsonl \ --output-dir ./outputs # Audit chain `target_id` HASH'LENMİŞ ŞEKİLDE # `data.erasure_requested` → `data.erasure_completed` (veya -# `data.erasure_failed`) damgalar. Eğer satırı eğiten bir model -# `final_model/`e sahipse `data.erasure_warning_memorisation` DA -# yangın eder ve memorisation residual riskini özneye bildirmek -# zorundasınız. +# `data.erasure_failed`) damgalar. Eğer `--output-dir` altında doğrudan bir +# `final_model/` dizini varsa `data.erasure_warning_memorisation` DA +# tetiklenir; satırı hangi modelin gördüğünü söylemez, bu yüzden +# corpus ile eğitilmiş her modeli kapsam içinde sayın ve memorisation +# kalan ezberleme riskini veri sahibine bildirin. ``` Cross-tool digest correlation (purge `target_id` == reverse-pii -`query_hash` aynı `output_dir`'deki aynı identifier için) Wave 3 -follow-up testleriyle pin'lenmiştir. +`query_hash`: aynı `output_dir`, aynı değer ve aynı +`FORGELM_AUDIT_SECRET` durumu, `--salt-source` olmadan) testlerle +pin'lenmiştir. ## Yaygın tuzaklar diff --git a/docs/guides/iso_soc2_deployer_guide.md b/docs/guides/iso_soc2_deployer_guide.md index 9d020375..74e23dcd 100644 --- a/docs/guides/iso_soc2_deployer_guide.md +++ b/docs/guides/iso_soc2_deployer_guide.md @@ -282,8 +282,9 @@ forgelm reverse-pii --query "alice@example.com" --type email \ --output-dir ./outputs data/*.jsonl # JSON envelope returns { matches: [...], match_count: N } # The audit chain stamps a `data.access_request_query` event with -# the QUERY HASHED (never raw — the salt protects against wordlist -# attacks against the audit log itself). +# the QUERY HASHED (never raw). The salted digest is personal data, +# not anonymous: keep `.forgelm_audit_salt` apart from the log (or set +# FORGELM_AUDIT_SECRET), or a guessed identifier can be tested against it. ``` Article 17 (right to erasure): @@ -293,15 +294,17 @@ forgelm purge --row-id "alice@example.com" --corpus data/2026Q2.jsonl \ --output-dir ./outputs # The audit chain stamps `data.erasure_requested` → # `data.erasure_completed` (or `data.erasure_failed`) with -# `target_id` HASHED. If a model trained on the row has -# `final_model/`, `data.erasure_warning_memorisation` ALSO fires -# and you must communicate the memorisation residual risk to the -# subject. +# `target_id` HASHED. If a `final_model/` directory exists directly +# under `--output-dir`, `data.erasure_warning_memorisation` ALSO fires; +# it does not say which model saw the row, so treat every model +# trained on the corpus as in scope and communicate the memorisation +# residual risk to the subject. ``` The cross-tool digest correlation (purge `target_id` == -reverse-pii `query_hash` for the same identifier in the same -`output_dir`) is pinned by Wave 3 follow-up tests. +reverse-pii `query_hash` for the same value in the same `output_dir` +under the same `FORGELM_AUDIT_SECRET` state, with no `--salt-source`) +is pinned by tests. ## Common pitfalls diff --git a/docs/qms/access_control-tr.md b/docs/qms/access_control-tr.md index 67f3ea60..217c9c9b 100644 --- a/docs/qms/access_control-tr.md +++ b/docs/qms/access_control-tr.md @@ -79,7 +79,7 @@ ID'leri sonsuza kadar chain'de kalır. Bir operatör ayrıldığında: ### 3.4 `FORGELM_AUDIT_SECRET` rotasyonu HMAC-chain imzalama anahtarı **audit koşumu başına** -`AuditLogger.__init__` (`forgelm/compliance.py:164`) içinde `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` +`forgelm/compliance.py` içindeki `AuditLogger.__init__` içinde `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` olarak türetilir (birleştirme; verifier `forgelm.compliance.verify_audit_log` aynı türetimi byte-byte yansıtır). Not: diff --git a/docs/qms/access_control.md b/docs/qms/access_control.md index 9e5f344a..4f4785d1 100644 --- a/docs/qms/access_control.md +++ b/docs/qms/access_control.md @@ -79,7 +79,7 @@ remain in the chain forever. When an operator leaves: The HMAC-chain signing key is derived **per audit run** as `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` (concatenation, see -`AuditLogger.__init__` at `forgelm/compliance.py:164`). Note: the per-output-dir salt +`AuditLogger.__init__` in `forgelm/compliance.py`). Note: the per-output-dir salt written to `/.forgelm_audit_salt` is a **distinct concern** — it salts identifier hashing inside `forgelm purge` / `forgelm reverse-pii` events (`_purge._resolve_salt` / diff --git a/docs/qms/encryption_at_rest-tr.md b/docs/qms/encryption_at_rest-tr.md index 7a0742a9..344453d0 100644 --- a/docs/qms/encryption_at_rest-tr.md +++ b/docs/qms/encryption_at_rest-tr.md @@ -25,7 +25,7 @@ Aşağıdaki ForgeLM-ürettiği asset sınıfları: | Model ağırlıkları — final | `/final_model/` | Eğitim verisi memorisation; rekabetçi moat | | Model ağırlıkları — staging | `/final_model.staging./` | Final ile aynı, artı pre-approval state | | Audit log | `/audit_log.jsonl` + `audit_log.jsonl.manifest.json` (genesis pin) | Operatör kimliği geçmişi; yapılandırma hashleri; uyumluluk için chain-of-custody — satır-seviyesi bütünlük per-line `_hmac` + `prev_hash` ile in-band'dir, ayrı bir `.sha256` sidecar yok | -| Per-output-dir salt | `/.forgelm_audit_salt` | Düşük-entropi identifier'ların salt'sız SHA-256'sı brute-force edilebilir — salt audit hash'ine wordlist direnci veren secret'tir | +| Per-output-dir salt | `/.forgelm_audit_salt` | Düşük-entropi identifier'ların salt'sız SHA-256'sı brute-force edilebilir — salt hazır bir wordlist'i işe yaramaz kılar, ancak hem log'a hem salt'a sahip olan biri tahminleri yine de deneyebilir | | Eğitim corpusu | (operatör-tedarikli; tipik olarak `data/*.jsonl`) | PII; ticari sırlar; müşteri verisi | | Quickstart-rendered config | (operatör path'i) | HF token'ları, webhook URL'leri, secret env-var isimleri | | Uyumluluk paketi (Annex IV) | `/compliance/` ZIP | Yukarıdaki her artefaktın aggregate formu | @@ -138,9 +138,10 @@ karşı offline dictionary saldırısı çalıştırmasına izin verir. - Sadece audit log'un şifreleme posture'ı ile eşleşen substrate'lere yedeklenir (HİÇBİR ZAMAN debug bucket'a plaintext). - Sadece karşılık gelen `` decommissioned olduğunda - rotate edilir — output-dir-ortasında rotate etmek `forgelm - verify-audit`'in önceki olaylar için salted-hash varsayımlarını - geçersiz kılar. + rotate edilir — output-dir-ortasında rotate etmek önceki ve sonraki + digest'ler arasındaki korelasyonu bozar (sonraki bir `forgelm + reverse-pii` sorgusu önceki bir `forgelm purge` olayıyla eşleşmez). + Salt'ı kullanmayan `forgelm verify-audit`'i etkilemez. **Defence-in-depth:** `FORGELM_AUDIT_SECRET`'i KMS'inizden export et böylece on-disk salt iki-bileşenli anahtarın yarısı haline gelir. @@ -215,9 +216,12 @@ ForgeLM kendisi şifreleme uygulamaz ama ŞUNLARI YAPAR: 3. **Şifreleme-sonrası bütünlüğü doğrula.** `forgelm verify-audit` chain-after-decryption'ın chain-as-emitted ile eşleştiğini teyit eder; substrate-seviyesi herhangi bir bozulma tespit edilebilirdir. -4. **Düşük-entropi identifier'ları hash'le.** Salted SHA-256, audit - chain'in kendisinin atıl-durumda gizli olması için şifrelenmesine - ihtiyaç olmadığı anlamına gelir — yalnız salt'ın. +4. **Identifier digest'lerini kişisel veri sayın.** Salted SHA-256 bir + identifier'ı anonim kılmaz (Gerekçe 26); ek bilgi ayrı tutulursa takma + adlandırma (GDPR Madde 4(5)) sayılır. Salt log ile aynı diskteyken ikisine + sahip olan herkes tahmin ettiği bir identifier'dan digest'i yeniden + hesaplayabilir. Audit log'u ve salt'ı aynı standartta şifreleyin ya da + secret'ı (`FORGELM_AUDIT_SECRET`) bir anahtar deposunda tutun. ## 6. Doğrulama checklist'i diff --git a/docs/qms/encryption_at_rest.md b/docs/qms/encryption_at_rest.md index 2c74a433..4b667b84 100644 --- a/docs/qms/encryption_at_rest.md +++ b/docs/qms/encryption_at_rest.md @@ -24,7 +24,7 @@ The following ForgeLM-produced asset classes: | Model weights — final | `/final_model/` | Memorisation of training data; competitive moat | | Model weights — staging | `/final_model.staging./` | Same as final, plus pre-approval state | | Audit log | `/audit_log.jsonl` + `audit_log.jsonl.manifest.json` (genesis pin) | Operator identity history; configuration hashes; chain-of-custody for compliance — line-level integrity is in-band via per-line `_hmac` + `prev_hash`, no separate `.sha256` sidecar | -| Per-output-dir salt | `/.forgelm_audit_salt` | Unsalted SHA-256 of low-entropy identifiers (emails, phones) is brute-forcible — the salt is the secret that gives the audit hash its wordlist resistance | +| Per-output-dir salt | `/.forgelm_audit_salt` | Unsalted SHA-256 of low-entropy identifiers (emails, phones) is brute-forcible — the salt makes a precomputed wordlist useless, but anyone who holds both the log and the salt can still test guesses | | Training corpus | (operator-supplied; typically `data/*.jsonl`) | PII; trade secrets; client data | | Quickstart-rendered config | (operator path) | HF tokens, webhook URLs, secret env-var names | | Compliance bundle (Annex IV) | `/compliance/` ZIP | Aggregated form of every above artefact | @@ -134,8 +134,10 @@ to recover the cleartext identifiers ForgeLM intentionally hashed. - Backed up only to substrates that match the audit log's encryption posture (NEVER plaintext to a debug bucket). - Rotated only when the corresponding `` is decommissioned - — rotating mid-output-dir invalidates `forgelm verify-audit`'s - salted-hash assumptions for prior events. + — rotating mid-output-dir breaks the correlation between earlier and + later digests (a later `forgelm reverse-pii` query no longer matches an + earlier `forgelm purge` event). It does not affect + `forgelm verify-audit`, which does not use the salt. **Defence-in-depth:** export `FORGELM_AUDIT_SECRET` from your KMS so the on-disk salt becomes one half of a two-component key. An attacker @@ -206,9 +208,13 @@ ForgeLM does not implement encryption itself but DOES: 3. **Verify post-encryption integrity.** `forgelm verify-audit` confirms the chain-after-decryption matches the chain-as-emitted; any substrate-level corruption is detectable. -4. **Hash low-entropy identifiers.** Salted SHA-256 means the audit - chain itself does not need encryption to be confidential at rest - — only the salt does. +4. **Treat identifier digests as personal data.** Salted SHA-256 does + not make an identifier anonymous (recital 26); it is pseudonymisation + (GDPR Article 4(5)) only if the additional information is kept + separately. With the salt on the same disk as the log, anyone holding + both can recompute a digest from a guessed identifier. Encrypt the + audit log and the salt to the same standard, or hold the secret + (`FORGELM_AUDIT_SECRET`) in a key store. ## 6. Verification checklist diff --git a/docs/qms/risk_treatment_plan-tr.md b/docs/qms/risk_treatment_plan-tr.md index 073d0c25..ee0f437d 100644 --- a/docs/qms/risk_treatment_plan-tr.md +++ b/docs/qms/risk_treatment_plan-tr.md @@ -79,7 +79,7 @@ ekler. |---|---| | Açıklama | `audit_log.jsonl`'a write erişimi olan adversary girişleri yeniden yazıp bir dağıtımı gizler / onaylanmamış bir modeli onaylar | | L × I (inherent) | Low × High = MED | -| Treatment | Append-only `O_APPEND` + `flock` + per-line `fsync`; HMAC chain (per-line `_hmac` alanı, per-run imzalama anahtarı `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` olarak türetilir — `forgelm/compliance.py:164` — per-output-dir `.forgelm_audit_salt` ayrı bir konudur, purge / reverse-pii tanımlayıcı hash'lerini salt'lar ve chain-key türetimine KATILMAZ); SHA-256 prev-hash chain; genesis manifest sidecar (`_check_genesis_manifest` truncate-and-resume reddeder); `forgelm verify-audit` uçtan uca doğrular | +| Treatment | Append-only `O_APPEND` + `flock` + per-line `fsync`; HMAC chain (per-line `_hmac` alanı, per-run imzalama anahtarı `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` olarak türetilir — `forgelm/compliance.py` içindeki `AuditLogger.__init__` — per-output-dir `.forgelm_audit_salt` ayrı bir konudur, purge / reverse-pii tanımlayıcı hash'lerini salt'lar ve chain-key türetimine KATILMAZ); SHA-256 prev-hash chain; genesis manifest sidecar (`_check_genesis_manifest` truncate-and-resume reddeder); `forgelm verify-audit` uçtan uca doğrular | | Residual L × I | Low × Low = LOW | | Sahip | AI Officer | | Review cadence | Continuous (`forgelm verify-audit` cron) | diff --git a/docs/qms/risk_treatment_plan.md b/docs/qms/risk_treatment_plan.md index 5b5ef08b..cf6d5efe 100644 --- a/docs/qms/risk_treatment_plan.md +++ b/docs/qms/risk_treatment_plan.md @@ -78,7 +78,7 @@ section §4. |---|---| | Description | Adversary with write access to `audit_log.jsonl` rewrites entries to hide a deployment / approve a model that wasn't approved | | L × I (inherent) | Low × High = MED | -| Treatment | Append-only `O_APPEND` + `flock` + `fsync` per line; HMAC chain (per-line `_hmac` field, per-run signing key derived as `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` per `forgelm/compliance.py:164` — the per-output-dir `.forgelm_audit_salt` is a distinct concern that salts purge / reverse-pii identifier hashes and does NOT participate in chain-key derivation); SHA-256 prev-hash chain; genesis manifest sidecar (`_check_genesis_manifest` refuses truncate-and-resume); `forgelm verify-audit` validates end-to-end | +| Treatment | Append-only `O_APPEND` + `flock` + `fsync` per line; HMAC chain (per-line `_hmac` field, per-run signing key derived as `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` per `AuditLogger.__init__` in `forgelm/compliance.py` — the per-output-dir `.forgelm_audit_salt` is a distinct concern that salts purge / reverse-pii identifier hashes and does NOT participate in chain-key derivation); SHA-256 prev-hash chain; genesis manifest sidecar (`_check_genesis_manifest` refuses truncate-and-resume); `forgelm verify-audit` validates end-to-end | | Residual L × I | Low × Low = LOW | | Owner | AI Officer | | Review cadence | Continuous (`forgelm verify-audit` cron) | diff --git a/docs/qms/sop_change_management-tr.md b/docs/qms/sop_change_management-tr.md index 8a3d1a4b..76dfc97a 100644 --- a/docs/qms/sop_change_management-tr.md +++ b/docs/qms/sop_change_management-tr.md @@ -106,8 +106,8 @@ CAB'sıdır: atomik rename ile `/final_model/`'e promote olur. 5. Reddedilirse `human_approval.rejected` event yangın eder; model forensic inceleme için `final_model.staging./`'de kalır, - retention süresi dolana kadar (`retention.staging_ttl_days` — - config'te). + biri onu silene kadar (`forgelm purge --check-policy`, onu + `retention.staging_ttl_days`'ten eski olduğunda raporlar). Bu, operatöre SOC 2 CC8.1 kanıtı verir: onay kapısı etkinleştirildiğinde her model promotion kaydedilen onaylayıcı kimliğine atfedilir ve forensic olarak kayıtlıdır. Eğitici ile onaylayıcıyı diff --git a/docs/qms/sop_change_management.md b/docs/qms/sop_change_management.md index cffc7820..e96e9b42 100644 --- a/docs/qms/sop_change_management.md +++ b/docs/qms/sop_change_management.md @@ -102,7 +102,8 @@ in-pipeline CAB: `/final_model/` via atomic rename. 5. If rejected, `human_approval.rejected` event fires; model stays in `final_model.staging./` for forensic review until - retention expires (`retention.staging_ttl_days` in the config). + someone removes it (`forgelm purge --check-policy` reports it once it + is older than `retention.staging_ttl_days`). This gives a deployer SOC 2 CC8.1 evidence: when the approval gate is enabled, every model promotion is attributed to the recorded approver identity and forensically recorded. diff --git a/docs/qms/sop_data_management-tr.md b/docs/qms/sop_data_management-tr.md index 782550cd..3ca07953 100644 --- a/docs/qms/sop_data_management-tr.md +++ b/docs/qms/sop_data_management-tr.md @@ -112,7 +112,7 @@ Manuel kontroller: - Eğitim verisi sürüm kontrollü veya immutable depolamada saklanır - SHA-256 fingerprint her eğitim koşumu için `data_provenance.json`'da kaydedilir -- Eğitim verisi en az **5 yıl** model artefaktlarıyla birlikte saklanır +- Eğitim verisi model artefaktlarıyla birlikte yalnızca saklama politikanızın belirlediği ve gerekçelendirdiği süre boyunca saklanır: kişisel veri yalnızca gerektiği kadar tutulabilir (GDPR Madde 5(1)(e)) ve EU AI Act Madde 18 gibi bir dokümantasyon saklama yükümlülüğü kişisel veriyi saklamayı tek başına haklı kılmaz. `data_provenance.json` içindeki SHA-256 parmak izi satır içeriği taşımaz ve corpus'tan daha uzun süre saklanabilir - Erişim yetkili ML ekibi üyeleriyle sınırlıdır ## 7. İnceleme diff --git a/docs/qms/sop_data_management.md b/docs/qms/sop_data_management.md index 6bf825cc..6bd4713b 100644 --- a/docs/qms/sop_data_management.md +++ b/docs/qms/sop_data_management.md @@ -111,7 +111,7 @@ Manual checks: - Training data stored in version-controlled or immutable storage - SHA-256 fingerprint recorded in `data_provenance.json` for every training run -- Retain training data for minimum **5 years** alongside model artifacts +- Retain training data alongside model artifacts only for the period your retention policy sets and justifies: personal data may be kept only as long as necessary (GDPR Article 5(1)(e)), and a documentation-retention duty such as EU AI Act Article 18 does not by itself justify keeping personal data. The SHA-256 fingerprint in `data_provenance.json` holds no row content and can be kept longer than the corpus - Access restricted to authorized ML team members ## 7. Review diff --git a/docs/qms/sop_incident_response-tr.md b/docs/qms/sop_incident_response-tr.md index b1aabf3d..d1ba101c 100644 --- a/docs/qms/sop_incident_response-tr.md +++ b/docs/qms/sop_incident_response-tr.md @@ -244,17 +244,21 @@ olabilir. **Tetik:** Veri sahibi operatörün DSAR portalı veya iletişim formu üzerinden erişim talebi gönderir. -**Önem:** Medium (regülasyon deadline 30 gün). +**Önem:** Medium (yanıt, talebin alınmasından itibaren bir ay içinde, GDPR Madde 12(3); talebin karmaşıklığı ve sayısı gerektiriyorsa iki ay daha uzatılabilir, uzatma ve gerekçesi ilk ay içinde bildirilir; KVKK Madde 13(2)'de 30 gün). **Runbook:** 1. [ ] **Sahip kimliğini doğrula** operatörün DSAR prosedürü başına. 2. [ ] **`forgelm reverse-pii --query --type data/*.jsonl --output-dir ` çalıştır**. -3. [ ] **Hash-mask scan** ForgeLM'in maskelediği herhangi bir - korpus için (`forgelm reverse-pii --query - --salt-source per_dir --output-dir `). -4. [ ] **Yanıt mektubu oluştur** DSAR şablonu başına; koşumun +3. [ ] **Diğer kopyaları da ara.** Eşleşme büyük/küçük harfe duyarlıdır + ve ham dosya metnini karşılaştırır; yazım ve harf varyantlarını da + ara; `forgelm ingest --pii-mask` digest değil `[REDACTED]` yazar, + bu yüzden hash-mask taraması (`--salt-source`) yalnızca ForgeLM + dışında hash'lenmiş korpuslar içindir; ham kaynak dokümanları ayrıca ara. +4. [ ] **Yanıt mektubu oluştur** DSAR şablonu başına: verinin bir + kopyası ve Madde 15(1)(a)-(h) bilgileri; başkalarının verisini + açığa çıkarmamaya dikkat et (Madde 15(4)); koşumun `data.access_request_query` audit-event id'sine atıf yap. 5. [ ] **Yanıt mektubunu sakla** audit chain yanında. @@ -262,8 +266,10 @@ olabilir. **Tetik:** Sahip silme talebi gönderir. -**Önem:** Medium (regülasyon deadline 30 gün; bazı AB üye -ülkeleri daha kısa yanıt gerektirir). +**Önem:** Medium (yanıt, talebin alınmasından itibaren bir ay içinde, +GDPR Madde 12(3); talebin karmaşıklığı ve sayısı gerektiriyorsa iki ay +daha uzatılabilir, uzatma ve gerekçesi ilk ay içinde bildirilir; KVKK +Madde 13(2)'de 30 gün). **Runbook:** @@ -271,8 +277,10 @@ olabilir. 2. [ ] **`forgelm purge --row-id --corpus data/.jsonl --output-dir ` çalıştır**. 3. [ ] **`data.erasure_warning_memorisation` olayını kontrol et** — - satırı eğiten bir model `final_model/` artefaktına sahipse, - memorisation residual riski uygulanır. + yalnızca `--output-dir` altında doğrudan bir `final_model/` + dizini varsa tetiklenir ve satırı hangi modelin gördüğünü + söylemez; uyarı tetiklenmese bile corpus ile eğitilmiş her + modeli kapsam içinde say. 4. [ ] **Memorisation caveat'ı sahibe bildir** yanıt mektubunda (şablon: "satırı sildik ama önceden eğitilmiş bir model bunu memorise etmiş olabilir; uygun şekilde yeniden eğiteceğiz veya diff --git a/docs/qms/sop_incident_response.md b/docs/qms/sop_incident_response.md index bf322545..1265760f 100644 --- a/docs/qms/sop_incident_response.md +++ b/docs/qms/sop_incident_response.md @@ -246,17 +246,21 @@ that an attacker may have observed. **Trigger:** Data subject submits an access request via deployer's DSAR portal or contact form. -**Severity:** Medium (regulatory deadline 30 days). +**Severity:** Medium (response due within one month of receipt, GDPR Art. 12(3), extendable by two further months where necessary given the complexity and number of requests, with notice and reasons in the first month; 30 days under KVKK Art. 13(2)). **Runbook:** 1. [ ] **Verify subject identity** per deployer's DSAR procedure. 2. [ ] **Run** `forgelm reverse-pii --query --type data/*.jsonl --output-dir ` -3. [ ] **Hash-mask scan** for any corpus that ForgeLM masked - (`forgelm reverse-pii --query --salt-source per_dir - --output-dir `). -4. [ ] **Compose response letter** per DSAR template; cite the run's +3. [ ] **Search the other copies too.** The match is case-sensitive + and compares raw file text, so search spelling and case variants; + `forgelm ingest --pii-mask` writes `[REDACTED]`, not digests, so a + hash-mask scan (`--salt-source`) is only for corpora hashed outside + ForgeLM; search the raw source documents separately. +4. [ ] **Compose response letter** per DSAR template: a copy of the + data and the Art. 15(1)(a)-(h) information, taking care not to + expose other people's data (Art. 15(4)); cite the run's `data.access_request_query` audit-event id. 5. [ ] **Retain** the response letter alongside the audit chain. @@ -264,8 +268,10 @@ DSAR portal or contact form. **Trigger:** Subject submits an erasure request. -**Severity:** Medium (regulatory deadline 30 days; some EU member -states require shorter response). +**Severity:** Medium (response due within one month of receipt, GDPR +Art. 12(3), extendable by two further months where necessary given the +complexity and number of requests, with notice and reasons in the first +month; 30 days under KVKK Art. 13(2)). **Runbook:** @@ -273,8 +279,10 @@ states require shorter response). 2. [ ] **Run** `forgelm purge --row-id --corpus data/.jsonl --output-dir `. 3. [ ] **Check** for `data.erasure_warning_memorisation` event — - if a model trained on the row has a `final_model/` artefact, - memorisation residual risk applies. + it fires only when a `final_model/` directory exists directly + under `--output-dir` and does not say which model saw the row, so + treat every model trained on the corpus as in scope even when no + warning fires. 4. [ ] **Communicate the memorisation caveat** to the subject in the response letter (template: "we deleted the row but a previously-trained model may have memorised it; we will diff --git a/docs/qms/sop_model_training-tr.md b/docs/qms/sop_model_training-tr.md index 58031c32..84a8e259 100644 --- a/docs/qms/sop_model_training-tr.md +++ b/docs/qms/sop_model_training-tr.md @@ -63,7 +63,7 @@ Manuel: ### 4.5 Kayıt Tutma -- Uyumluluk artefaktları en az **5 yıl** saklanır (veya regülasyon gerektirdiği kadar) +- Uyumluluk artefaktları saklama politikanızın belirlediği süre boyunca saklanır (örneğin EU AI Act Madde 18, yüksek riskli sistem sağlayıcılarından teknik dokümantasyonu, kalite yönetimi dokümantasyonunu ve AB uygunluk beyanını sistem piyasaya arz edildikten veya hizmete alındıktan sonra 10 yıl saklamalarını ister; size neyin uygulandığını kontrol edin) - Kanıt paketi: `forgelm --config job.yaml --compliance-export ./archive/` - Mümkünse immutable/append-only depolamada sakla diff --git a/docs/qms/sop_model_training.md b/docs/qms/sop_model_training.md index 564956a1..f06ac153 100644 --- a/docs/qms/sop_model_training.md +++ b/docs/qms/sop_model_training.md @@ -62,7 +62,7 @@ Manual: ### 4.5 Record Retention -- Compliance artifacts retained for minimum **5 years** (or as required by regulation) +- Compliance artifacts retained for the period your retention policy sets (for example, EU AI Act Article 18 asks providers of high-risk systems to keep technical documentation, quality-management documentation and the EU declaration of conformity for 10 years after the system is placed on the market or put into service; check what applies to you) - Evidence bundle: `forgelm --config job.yaml --compliance-export ./archive/` - Store in immutable/append-only storage if available diff --git a/docs/qms/statement_of_applicability-tr.md b/docs/qms/statement_of_applicability-tr.md index 3f93b03f..d0937376 100644 --- a/docs/qms/statement_of_applicability-tr.md +++ b/docs/qms/statement_of_applicability-tr.md @@ -130,7 +130,7 @@ ForgeLM-spesifik kontrol envanteriyle ilgilidir. | A.8.21 Ağ hizmetlerinin güvenliği | YES | TLS-only webhooks (HTTPS-only + SSRF guard); audit-log chain HMAC `FORGELM_AUDIT_SECRET` üzerinden (not: webhook **gövdeleri** HMAC ile imzalanmaz — hedef-tarafı authenticity alıcı sistemin sorumluluğundadır) | TLS 1.2+ enforcement | | A.8.22 Ağ ayrımı | YES | VPC / subnet design | Benimse | | A.8.23 Web filtreleme | YES | Egress proxy | Benimse | -| A.8.24 Kriptografi kullanımı | YES | SHA-256 + HMAC-SHA-256 (audit chain key = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, bkz. `AuditLogger.__init__` — `forgelm/compliance.py:164`); ayrıca `forgelm purge` / `forgelm reverse-pii` için salted SHA-256 identifier hashing (`_purge._resolve_salt`, ayrı bir konu — chain-key türetimine katılmaz) | `FORGELM_AUDIT_SECRET` için KMS | +| A.8.24 Kriptografi kullanımı | YES | SHA-256 + HMAC-SHA-256 (audit chain key = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, bkz. `forgelm/compliance.py` içindeki `AuditLogger.__init__`); ayrıca `forgelm purge` / `forgelm reverse-pii` için salted SHA-256 identifier hashing (`_purge._resolve_salt`, ayrı bir konu — chain-key türetimine katılmaz) | `FORGELM_AUDIT_SECRET` için KMS | | A.8.25 Güvenli geliştirme yaşam döngüsü | YES | `docs/standards/code-review.md`, `release.md`, CI gate'leri | SDLC framework | | A.8.26 Uygulama güvenliği gereksinimleri | YES | F-compliance-110 strict gate; Pydantic validation; `_reverse_pii`'da ReDoS guard | App-seviyesi tehdit modelleme | | A.8.27 Güvenli sistem mimarisi ve mühendislik prensipleri | YES | Append-only audit log mimarisi; HMAC chain; lazy import; SSRF guard | Defence-in-depth | diff --git a/docs/qms/statement_of_applicability.md b/docs/qms/statement_of_applicability.md index 26767b04..d053a813 100644 --- a/docs/qms/statement_of_applicability.md +++ b/docs/qms/statement_of_applicability.md @@ -129,7 +129,7 @@ ForgeLM-specific control inventory. | A.8.21 Security of network services | YES | TLS-only webhooks (HTTPS-only with SSRF guard); audit-log chain HMAC via `FORGELM_AUDIT_SECRET` (note: webhook **bodies** are not HMAC-signed — destination-side authenticity is the receiving system's responsibility) | TLS 1.2+ enforcement | | A.8.22 Segregation of networks | YES | VPC / subnet design | Adopt | | A.8.23 Web filtering | YES | Egress proxy | Adopt | -| A.8.24 Use of cryptography | YES | SHA-256 + HMAC-SHA-256 (audit chain key = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, see `AuditLogger.__init__` at `forgelm/compliance.py:164`); separately, salted SHA-256 identifier hashing for `forgelm purge` / `forgelm reverse-pii` (`_purge._resolve_salt`, distinct concern — does not participate in chain-key derivation) | KMS for `FORGELM_AUDIT_SECRET` | +| A.8.24 Use of cryptography | YES | SHA-256 + HMAC-SHA-256 (audit chain key = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, see `AuditLogger.__init__` in `forgelm/compliance.py`); separately, salted SHA-256 identifier hashing for `forgelm purge` / `forgelm reverse-pii` (`_purge._resolve_salt`, distinct concern — does not participate in chain-key derivation) | KMS for `FORGELM_AUDIT_SECRET` | | A.8.25 Secure development life cycle | YES | `docs/standards/code-review.md`, `release.md`, CI gates | SDLC framework | | A.8.26 Application security requirements | YES | F-compliance-110 strict gate; Pydantic validation; ReDoS guard in `_reverse_pii` | App-level threat modelling | | A.8.27 Secure system architecture and engineering principles | YES | Append-only audit log architecture; HMAC chain; lazy import; SSRF guard | Defence-in-depth | diff --git a/docs/reference/audit_event_catalog-tr.md b/docs/reference/audit_event_catalog-tr.md index 452d4e73..d2a8b755 100644 --- a/docs/reference/audit_event_catalog-tr.md +++ b/docs/reference/audit_event_catalog-tr.md @@ -80,7 +80,7 @@ Hash zinciri, satır diske düştükten (`flush` + `fsync`) sonra ilerler; kirli | `data.erasure_failed` | Disk operasyonu raise etti VEYA eşleşen satır/koşum bulunamadı VEYA çoklu-satır policy belirsizliği reddetti. | Tüm `requested` field'ları + `error_class`, `error_message` | 17 | | `data.erasure_warning_memorisation` | Row erasure × `--output-dir` altında doğrudan bir `final_model/` dizini mevcut (birebir ad; `--output-dir` varsayılan olarak corpus'un dizinidir, bu yüzden eğitim çıktı dizininizi verin). ForgeLM corpus'u hangi koşumların tükettiğini belirlemez. | Tüm `completed` field'ları + `affected_run_ids` | 17 | | `data.erasure_warning_synthetic_data_present` | Row erasure × `output_dir`'de `synthetic_data*.jsonl` mevcut. | Tüm `completed` field'ları + `synthetic_files` | 17 | -| `data.erasure_warning_external_copies` | Yüklü config boş-olmayan `webhook` block'u içeriyor; downstream tüketiciler bildirim almış olabilir. | Tüm `completed` field'ları + `webhook_targets` (redact'li URL'ler) | 17 | +| `data.erasure_warning_external_copies` | Yalnızca `--config` ile row-modu purge: config'in `webhook` bloğu boş değil (literal `url` ya da export edilmiş `url_env`); downstream tüketiciler bildirim almış olabilir. | Tüm `completed` field'ları + `webhook_targets` (redact'li URL'ler) | 17 | ### Madde 15 — GDPR Erişim Hakkı (Phase 38 — `forgelm reverse-pii`) diff --git a/docs/reference/audit_event_catalog.md b/docs/reference/audit_event_catalog.md index d5b93ebd..2eed9532 100644 --- a/docs/reference/audit_event_catalog.md +++ b/docs/reference/audit_event_catalog.md @@ -80,7 +80,7 @@ The hash chain advances after the line lands on disk (`flush` + `fsync`), so an | `data.erasure_failed` | Disk operation raised, OR no matching row/run found, OR multi-row policy refused on ambiguity. | All `requested` fields + `error_class`, `error_message` | 17 | | `data.erasure_warning_memorisation` | Row erasure × a `final_model/` directory exists directly under `--output-dir` (literal name; `--output-dir` defaults to the corpus's directory, so pass your training output directory). ForgeLM does not determine which runs consumed the corpus. | All `completed` fields + `affected_run_ids` | 17 | | `data.erasure_warning_synthetic_data_present` | Row erasure × `synthetic_data*.jsonl` exists in `output_dir`. | All `completed` fields + `synthetic_files` | 17 | -| `data.erasure_warning_external_copies` | Loaded config has a non-empty `webhook` block; downstream consumers may have received notices. | All `completed` fields + `webhook_targets` (redacted URLs) | 17 | +| `data.erasure_warning_external_copies` | Row-mode purge with `--config` only: the config's `webhook` block is non-empty (literal `url`, or `url_env` exported); downstream consumers may have received notices. | All `completed` fields + `webhook_targets` (redacted URLs) | 17 | ### Article 15 — GDPR Right-of-Access (Phase 38 — `forgelm reverse-pii`) diff --git a/docs/reference/compliance_summary-tr.md b/docs/reference/compliance_summary-tr.md index 1d9b3def..03d306bb 100644 --- a/docs/reference/compliance_summary-tr.md +++ b/docs/reference/compliance_summary-tr.md @@ -36,8 +36,8 @@ ForgeLM kutudan çıktığı gibi şunları ship eder: `forgelm verify-integrity`. - **EU AI Act Madde 17** QMS şablonları: `docs/qms/` (Wave 0 baseline + Wave 4 ISO eklemeleri). -- **GDPR Madde 15** erişim hakkı: `forgelm reverse-pii`. -- **GDPR Madde 17** silinme hakkı: `forgelm purge`. +- **GDPR Madde 15**: `forgelm reverse-pii`, bir identifier'ı içeren satırları bulur (erişim yanıtının bir girdisi). +- **GDPR Madde 17**: `forgelm purge`, bir corpus'tan satır kaldırır (silmenin bir adımı). - **ISO 27001 / SOC 2 uyumluluğu** — bkz. Wave 4 design doc + deployer rehberi. @@ -259,9 +259,9 @@ Tam alan semantiği ve config yüzeyi: ### GDPR Madde 15 + 17 (Wave 2b + Wave 3) -- Madde 17 silme: `forgelm purge --row-id --corpus +- Madde 17 (silmenin bir adımı): `forgelm purge --row-id --corpus data/file.jsonl`, salted-hash audit ile (`data.erasure_*` event'leri). -- Madde 15 erişim: `forgelm reverse-pii --query --type +- Madde 15 (erişim yanıtının bir girdisi): `forgelm reverse-pii --query --type email|phone|... data/*.jsonl`, salted-hash audit ile (`data.access_request_query` event'i). diff --git a/docs/reference/compliance_summary.md b/docs/reference/compliance_summary.md index 1bd714c3..dd78419f 100644 --- a/docs/reference/compliance_summary.md +++ b/docs/reference/compliance_summary.md @@ -37,8 +37,8 @@ ForgeLM ships out-of-the-box: post-deployment verification via `forgelm verify-integrity`. - **EU AI Act Article 17** QMS templates in `docs/qms/` (Wave 0 baseline + Wave 4 ISO additions). -- **GDPR Article 15** right-of-access via `forgelm reverse-pii`. -- **GDPR Article 17** right-to-erasure via `forgelm purge`. +- **GDPR Article 15**: `forgelm reverse-pii` finds the rows that contain an identifier (an input to an access response). +- **GDPR Article 17**: `forgelm purge` removes rows from a corpus (one step of an erasure). - **ISO 27001 / SOC 2 alignment** — see Wave 4 design doc + deployer guide. @@ -258,9 +258,9 @@ Full field semantics and the config surface: ### GDPR Article 15 + 17 (Wave 2b + Wave 3) -- Article 17 erasure: `forgelm purge --row-id --corpus +- Article 17 (one step of an erasure): `forgelm purge --row-id --corpus data/file.jsonl` with salted-hash audit (`data.erasure_*` events). -- Article 15 access: `forgelm reverse-pii --query --type +- Article 15 (an input to an access response): `forgelm reverse-pii --query --type email|phone|... data/*.jsonl` with salted-hash audit (`data.access_request_query` event). diff --git a/docs/reference/configuration-tr.md b/docs/reference/configuration-tr.md index 1bd2349b..bc4d65e7 100644 --- a/docs/reference/configuration-tr.md +++ b/docs/reference/configuration-tr.md @@ -232,7 +232,7 @@ training: | `track_categories` | bool | `false` | Llama Guard S1-S14 zarar kategorilerini ayrıştır | | `severity_thresholds` | dict | `null` | Ciddiyet bazlı sınırlar: `{"critical": 0, "high": 0.01}` | | `batch_size` | int | `8` | Güvenlik değerlendirmesi için batched generation boyutu. `1` batching'i devre dışı bırakır; geniş VRAM'de throughput için artırın, küçük VRAM'de OOM riskini azaltmak için düşürün. | -| `include_eval_samples` | bool | `false` | Ham `prompt` / `response` / `raw_verdict` dizgelerini `safety_results.json`'a yazar. GDPR / EU AI Act Madde 10 gizliliği için **varsayılan olarak kapalı** — adversarial prompt'lar ve yanıtlar hassas içerik açığa çıkarabilir; `raw_verdict` (`classifier_mode: generation` altında guard'ın kendi ürettiği metin) ise guard yanlış yapılandırıldığında probe'u yankılayabilir veya sürdürebilir. Yalnızca hata ayıklama için açın. | +| `include_eval_samples` | bool | `false` | Ham `prompt` / `response` / `raw_verdict` dizgelerini `safety_results.json`'a yazar. GDPR Madde 25(2)'deki varsayılan veri koruma yükümlülüğü gereği **kapalıdır** — adversarial prompt'lar ve yanıtlar hassas içerik açığa çıkarabilir; `raw_verdict` (`classifier_mode: generation` altında guard'ın kendi ürettiği metin) ise guard yanlış yapılandırıldığında probe'u yankılayabilir veya sürdürebilir. Yalnızca hata ayıklama için açın. | #### `evaluation.llm_judge` (İsteğe bağlı) @@ -247,7 +247,7 @@ training: | `min_score` | float | `5.0` | Minimum ortalama puan (1-10) | | `min_valid_fraction` | float | `0.8` | Ortalamanın kanıt sayılabilmesi için parse edilebilir judge puanı üretmesi gereken eval prompt oranı. Ortalama **yalnızca** parse edilebilen puanlar üzerinden hesaplanır — başarısız bir judge çağrısı düşük sayılmaz, atılır — dolayısıyla bu taban olmadan iki yüz prompt içinden tek bir `9` puanı `min_score: 8` kapısını geçiyordu. Bu oranın altında kapı, `Average judge score … below minimum`'dan ayrı bir `Insufficient valid judge evidence` gerekçesiyle başarısız olur. `0.0`, 0.11.0'a kadarki davranışa döner. | | `batch_size` | int | `8` | LLM-hakim turunda puanlanan (prompt, completion) çift sayısı. `1` batching'i devre dışı bırakır. | -| `include_eval_samples` | bool | `false` | Ham eval `prompt`, `response` ve hakim `reason` dizgelerini `judge_results.json`'a yazar. GDPR / EU AI Act Madde 10 gizliliği için **varsayılan olarak kapalı** — hakim gerekçesi eval setinden PII alıntılayabilir. Yalnızca hata ayıklama için açın. | +| `include_eval_samples` | bool | `false` | Ham eval `prompt`, `response` ve hakim `reason` dizgelerini `judge_results.json`'a yazar. GDPR Madde 25(2)'deki varsayılan veri koruma yükümlülüğü gereği **kapalıdır** — hakim gerekçesi eval setinden PII alıntılayabilir. Yalnızca hata ayıklama için açın. | > **Hakim girdisi kırpma:** her puanlama prompt'u oluşturulurken hakim, > eval prompt'unun en fazla ilk **500 karakterini** ve model yanıtının en fazla @@ -259,29 +259,37 @@ training: > için `min_score` ayarlarken bunu göz önünde bulundurun. > > **Kaldırıldı:** `evaluation.staging_ttl_days`, -> [`retention.staging_ttl_days`](#retention-isteğe-bağlı--gdpr-madde-17-silme-ufukları) +> [`retention.staging_ttl_days`](#retention-isteğe-bağlı--saklama-ufukları) > tarafından devralınmış ve v0.8.0'da kaldırılmıştır. `retention.staging_ttl_days` > kullanın; eski anahtarı hâlâ set eden YAML dosyaları `EXIT_CONFIG_ERROR` ile > config-load başarısızlığına yol açar. --- -## `retention` (İsteğe bağlı — GDPR Madde 17 silme ufukları) +## `retention` (İsteğe bağlı — saklama ufukları) -Uyumluluk, eğitim ve değerlendirme artefaktları için saklama ufuklarını -belirler. Ufuklar GDPR Madde 5(1)(e) "saklama sınırlaması" ve Madde 17 -"silme hakkı" tarihlerini onurlandırır. Ufuklar **raporlanır**, zorlanmaz: -`forgelm purge --check-policy` ufku aşanları listeler ve her zaman 0 ile çıkar; -hiçbir şey otomatik silinmez, engellenmez ya da zamanlanmaz. `enforce` anahtarı -ayrılmıştır: henüz hiçbir komut onu okumaz. +Uyumluluk, eğitim ve değerlendirme artefaktları için saklama ufuklarıdır. Bunlar, +GDPR Madde 5(1)(e) saklama sınırlaması ilkesini (kişisel veri yalnızca gerektiği +kadar tutulabilir; Tüzük sabit bir süre belirlemez) uygulamanıza yardımcı olan, +sizin belirlediğiniz sınırlardır. Değerleri kendi gereklilik (necessity) +değerlendirmenize göre seçin: varsayılanlar ForgeLM tercihleridir, yasal süre +değildir. + +Ufuklar **raporlanır, zorlanmaz**: `forgelm purge --check-policy` ufku aşanları +listeler ve kaç tane bulursa bulsun 0 ile çıkar (yüklenemeyen `--config` 1 ile +çıkar); hiçbir şey otomatik silinmez, engellenmez ya da zamanlanmaz. `retention:` +bloğu yoksa hiçbir şey kontrol edilmez; `retention: {}` varsayılanları devreye alır. +`enforce` anahtarı ayrılmıştır: henüz hiçbir komut onu dikkate almaz. | Alan | Tip | Varsayılan | Açıklama | |------|-----|-----------|----------| -| `audit_log_retention_days` | int | `1825` (~5 yıl) | `audit_log.jsonl` dosyasının Madde 5(1)(e) kapsamında "geciken" olarak işaretlenmeden önce saklanacağı gün sayısı. `0` süresiz saklamayı belirtir (Madde 17(3)(b) savunması). | -| `staging_ttl_days` | int | `7` | `forgelm reject` kararından sonra `final_model.staging./` dizininin planlı temizlenmeden önce saklanacağı gün sayısı. `0` süresiz saklama anlamına gelir. v0.8.0'da kaldırılan `evaluation.staging_ttl_days` yerine geçer. | -| `ephemeral_artefact_retention_days` | int | `90` | Uyumluluk paketleri, veri denetim raporları ve diğer çalışma kapsamlı türetilmiş artefaktların saklanma süresi (gün). `0` süresiz saklama. | -| `raw_documents_retention_days` | int | `90` | İngest edilmiş ham belgelerin (PDF / DOCX / EPUB / TXT / Markdown) operatörün ingestion-output dizininde saklanma süresi (gün). `0` süresiz saklama. | -| `enforce` | string | `"log_only"` | Ayrılmış politika modu. Henüz hiçbir komut onu okumaz: `"log_only"`, `"warn_on_excess"` ve `"block_on_excess"` kabul edilir ve aynı davranır; bir config son ikisinden birini ayarlarsa CLI uyarır. | +| `audit_log_retention_days` | int | `1825` (~5 yıl) | `forgelm purge --check-policy`'nin `audit_log.jsonl` dosyasını "süresi geçmiş" olarak raporladığı gün sayısı (Madde 5(1)(e)). 5 yıllık varsayılan bir ForgeLM tercihidir, yasal süre değildir. `0` bu raporu kapatır (hiçbir şey silinmez); log'u yalnızca girdiler gerekli kaldığı sürece ya da adlandırabileceğiniz belirli bir yasal saklama yükümlülüğü gerektirdiği sürece tutun. | +| `staging_ttl_days` | int | `7` | `forgelm purge --check-policy`'nin bir `final_model.staging./` dizinini (ve eski `final_model.staging/`'i) "süresi geçmiş" olarak raporladığı yaş (koşumun ilk audit event'inden, yoksa audit log'un ilk event'inden, o da yoksa dizinin mtime'ından); hiçbir şey otomatik silinmez ve `forgelm reject` kararının zamanı kullanılmaz. `0` bu raporu kapatır. v0.8.0'da kaldırılan `evaluation.staging_ttl_days` yerine geçer. | +| `ephemeral_artefact_retention_days` | int | `90` | `forgelm purge --check-policy`'nin `/compliance/` ve `/data_audit_report.json`'ı "süresi geçmiş" olarak raporladığı yaş (gün; audit log'un ilk event'inden, yoksa mtime'dan); hiçbir şey silinmez. `0` bu raporu kapatır. | +| `raw_documents_retention_days` | int | `90` | `forgelm purge --check-policy`'nin `/raw_documents/` veya `/ingestion_output/`'u (ingest edilmiş PDF / DOCX / EPUB / TXT / Markdown) "süresi geçmiş" olarak raporladığı yaş (gün; audit log'un ilk event'inden, yoksa mtime'dan); hiçbir şey silinmez. ForgeLM bu dizinleri oluşturmaz; ufuk yalnızca orada tuttuğunuz kaynak dokümanlara uygulanır. `0` bu raporu kapatır. | +| `enforce` | string | `"log_only"` | Ayrılmış politika modu. Hiçbir komut onu kullanmaz: `"log_only"`, `"warn_on_excess"` ve `"block_on_excess"` kabul edilir ve aynı davranır; bir config son ikisinden birini ayarlarsa eğitim ve `--dry-run` uyarır. | + +**Paylaşılan çıktı dizinleri.** `compliance/`, `data_audit_report.json` ve ham-belge dizinlerinin yaşı, dosyanın en son yazıldığı andan değil, audit log'un *ilk* event'inden sayılır. Birçok koşum için yeniden kullanılan bir dizinde bugün yazılmış bir paket de ufku aşmış olarak raporlanabilir: raporun listelediği bir şeyi silmeden önce dosyanın kendi tarihine bakın. > **Kaldırıldı:** `evaluation.staging_ttl_days` (v0.5.5 itibarıyla kullanımdan > kaldırılmıştı) v0.8.0'da kaldırılmıştır. Artık geçerli tek form diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 1af45a54..00abe768 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -233,7 +233,7 @@ across retries. Each retry attempt is logged to the audit trail. | `track_categories` | bool | `false` | Parse Llama Guard S1-S14 harm categories | | `severity_thresholds` | dict | `null` | Per-severity limits: `{"critical": 0, "high": 0.01, "medium": 0.05}` | | `batch_size` | int | `8` | Batched generation size for safety evaluation. `1` disables batching; raise for throughput on large VRAM, lower to reduce OOM risk on small VRAM. | -| `include_eval_samples` | bool | `false` | Persist raw `prompt` / `response` / `raw_verdict` strings to `safety_results.json`. **Off by default** for GDPR / EU AI Act Art. 10 privacy — adversarial prompts and responses may surface sensitive content, and `raw_verdict` (the guard's own generated text under `classifier_mode: generation`) can echo or continue the probe when the guard is misconfigured. Opt in only for debugging. | +| `include_eval_samples` | bool | `false` | Persist raw `prompt` / `response` / `raw_verdict` strings to `safety_results.json`. **Off by default** for GDPR Art. 25(2) data protection by default — adversarial prompts and responses may surface sensitive content, and `raw_verdict` (the guard's own generated text under `classifier_mode: generation`) can echo or continue the probe when the guard is misconfigured. Opt in only for debugging. | #### `evaluation.llm_judge` (Optional) @@ -248,7 +248,7 @@ across retries. Each retry attempt is logged to the audit trail. | `min_score` | float | `5.0` | Minimum average score (1-10) | | `min_valid_fraction` | float | `0.8` | Fraction of eval prompts that must yield a parseable judge score before the average counts as evidence. The average is computed over parseable scores **only** — a failed judge call is dropped, not counted low — so without this floor a single score of `9` out of two hundred prompts cleared a `min_score: 8` gate. Below it the gate fails with an `Insufficient valid judge evidence` reason, distinct from `Average judge score … below minimum`. `0.0` restores the behaviour through 0.11.0. | | `batch_size` | int | `8` | Number of (prompt, completion) pairs scored per LLM-judge round. `1` disables batching. | -| `include_eval_samples` | bool | `false` | Persist raw eval `prompt`, `response`, and judge `reason` strings to `judge_results.json`. **Off by default** for GDPR / EU AI Act Art. 10 privacy — judge reasoning can quote PII from the eval set. Opt in only for debugging. | +| `include_eval_samples` | bool | `false` | Persist raw eval `prompt`, `response`, and judge `reason` strings to `judge_results.json`. **Off by default** for GDPR Art. 25(2) data protection by default — judge reasoning can quote PII from the eval set. Opt in only for debugging. | > **Judge input truncation:** when building each scoring prompt the judge > sees at most the first **500 characters of the eval prompt** and the first @@ -260,28 +260,36 @@ across retries. Each retry attempt is logged to the audit trail. > for long-form fine-tunes. > > **Removed:** `evaluation.staging_ttl_days` was superseded by -> [`retention.staging_ttl_days`](#retention-optional--gdpr-article-17-erasure-horizons) +> [`retention.staging_ttl_days`](#retention-optional--retention-horizons) > and was removed in v0.8.0. Use `retention.staging_ttl_days`; YAML files that > still set the legacy key will fail config-load with `EXIT_CONFIG_ERROR`. --- -## `retention` (Optional — GDPR Article 17 erasure horizons) +## `retention` (Optional — retention horizons) -Defines maximum retention horizons for compliance, training, and evaluation -artefacts. Horizons honour GDPR Article 5(1)(e) "storage limitation" and -Article 17 "right to erasure" deadlines. The horizons are **reported**, not -enforced: `forgelm purge --check-policy` lists what is past them and always -exits 0, and nothing is deleted, blocked or scheduled automatically. The -`enforce` knob is reserved: no command reads it yet. +Retention horizons for compliance, training and evaluation artefacts. They are +limits you set to help apply the GDPR Article 5(1)(e) storage-limitation +principle (personal data may be kept only as long as necessary; the Regulation +sets no fixed period). Choose values from your own necessity assessment: the +defaults are ForgeLM choices, not legal periods. + +The horizons are **reported, not enforced**: `forgelm purge --check-policy` +lists what is past them and exits 0 however many it finds (an unloadable +`--config` exits 1), and nothing is deleted, blocked or scheduled +automatically. Without a `retention:` block nothing is checked at all; +`retention: {}` switches the defaults on. The `enforce` knob is reserved: no +command acts on it yet. | Field | Type | Default | Description | |-------|------|---------|-------------| -| `audit_log_retention_days` | int | `1825` (~5 years) | Days to retain `audit_log.jsonl` before flagging it as overdue under Article 5(1)(e). Set to `0` to retain indefinitely (Article 17(3)(b) defence). | -| `staging_ttl_days` | int | `7` | Days to retain `final_model.staging./` after a `forgelm reject` decision before scheduled cleanup. Set to `0` to retain indefinitely. Replaces the removed `evaluation.staging_ttl_days` (removed in v0.8.0). | -| `ephemeral_artefact_retention_days` | int | `90` | Days to retain compliance bundles, data audit reports, and other run-scoped derived artefacts. Set to `0` to retain indefinitely. | -| `raw_documents_retention_days` | int | `90` | Days to retain ingested raw documents (PDF / DOCX / EPUB / TXT / Markdown) under the operator's ingestion-output directory. Set to `0` to retain indefinitely. | -| `enforce` | string | `"log_only"` | Reserved policy mode. No command reads it yet: `"log_only"`, `"warn_on_excess"` and `"block_on_excess"` are all accepted and behave the same, and the CLI warns when a config sets either of the last two. | +| `audit_log_retention_days` | int | `1825` (~5 years) | Days after which `forgelm purge --check-policy` reports `audit_log.jsonl` as overdue (Article 5(1)(e)). The 5-year default is a ForgeLM choice, not a legal period. Set to `0` to stop reporting it (nothing is ever deleted); keep it only for as long as the entries stay necessary or a specific legal retention duty you can name requires it. | +| `staging_ttl_days` | int | `7` | Age (from the run's first audit event, else the audit log's first event, else the directory's mtime) after which `forgelm purge --check-policy` reports a `final_model.staging./` directory (and the legacy `final_model.staging/`) as overdue; nothing is deleted automatically and the time of a `forgelm reject` decision is not used. Set to `0` to stop reporting it. Replaces the removed `evaluation.staging_ttl_days` (removed in v0.8.0). | +| `ephemeral_artefact_retention_days` | int | `90` | Age in days after which `forgelm purge --check-policy` reports `/compliance/` and `/data_audit_report.json` as overdue (aged from the audit log's first event, else mtime); nothing is deleted. Set to `0` to stop reporting it. | +| `raw_documents_retention_days` | int | `90` | Age in days after which `forgelm purge --check-policy` reports `/raw_documents/` or `/ingestion_output/` (ingested PDF / DOCX / EPUB / TXT / Markdown) as overdue (aged from the audit log's first event, else mtime); nothing is deleted. ForgeLM does not create these directories, so the horizon applies only to source documents you keep there. Set to `0` to stop reporting it. | +| `enforce` | string | `"log_only"` | Reserved policy mode. No command acts on it: `"log_only"`, `"warn_on_excess"` and `"block_on_excess"` are all accepted and behave the same, and training and `--dry-run` warn when a config sets either of the last two. | + +**Shared output directories.** `compliance/`, `data_audit_report.json` and the raw-document directories are aged from the audit log's *first* event, not from when the file was last written. In a directory reused for many runs, a bundle written today can be reported as past its horizon: check the file's own date before deleting anything the report lists. > **Removed:** `evaluation.staging_ttl_days` (deprecated as of v0.5.5) was > removed in v0.8.0. `retention.staging_ttl_days` is now the only accepted form. diff --git a/docs/reference/iso27001_control_mapping-tr.md b/docs/reference/iso27001_control_mapping-tr.md index 9fb5ca31..e93db377 100644 --- a/docs/reference/iso27001_control_mapping-tr.md +++ b/docs/reference/iso27001_control_mapping-tr.md @@ -121,7 +121,7 @@ SoA'sının uçtan uca auditable olması için tamlık adına listelenmiştir. | A.8.21 Ağ hizmetlerinin güvenliği | FL-helps | TLS-only webhooks (HTTPS + SSRF guard); audit-log chain HMAC `FORGELM_AUDIT_SECRET` üzerinden (not: webhook **gövdeleri** HMAC ile imzalanmaz) | | A.8.22 Ağ ayrımı | OOS | — | | A.8.23 Web filtreleme | OOS | — | -| A.8.24 Kriptografi kullanımı | FL | SHA-256 + HMAC chain (per-run imzalama anahtarı = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, bkz. `forgelm/compliance.py:104-114`); ayrıca purge / reverse-pii için salted SHA-256 identifier hashing | +| A.8.24 Kriptografi kullanımı | FL | SHA-256 + HMAC chain (per-run imzalama anahtarı = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, bkz. `forgelm/compliance.py` içindeki `AuditLogger.__init__`); ayrıca purge / reverse-pii için salted SHA-256 identifier hashing | | A.8.25 Güvenli geliştirme yaşam döngüsü | FL-helps | `docs/standards/code-review.md`, `release.md`, CI gates | | A.8.26 Uygulama güvenliği gereksinimleri | FL-helps | F-compliance-110 strict gate; ReDoS guard | | A.8.27 Güvenli sistem mimarisi ve mühendislik prensipleri | FL-helps | Append-only audit log mimarisi | diff --git a/docs/reference/iso27001_control_mapping.md b/docs/reference/iso27001_control_mapping.md index 442c0617..77557751 100644 --- a/docs/reference/iso27001_control_mapping.md +++ b/docs/reference/iso27001_control_mapping.md @@ -122,7 +122,7 @@ completeness so the deployer's SoA is auditable end-to-end. | A.8.21 Security of network services | FL-helps | TLS-only webhooks (HTTPS + SSRF guard); audit-log chain HMAC via `FORGELM_AUDIT_SECRET` (note: webhook **bodies** are not HMAC-signed) | | A.8.22 Segregation of networks | OOS | — | | A.8.23 Web filtering | OOS | — | -| A.8.24 Use of cryptography | FL | SHA-256 + HMAC chain (per-run signing key = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, see `forgelm/compliance.py:104-114`); separately, salted SHA-256 identifier hashing for purge / reverse-pii | +| A.8.24 Use of cryptography | FL | SHA-256 + HMAC chain (per-run signing key = `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)`, see `AuditLogger.__init__` in `forgelm/compliance.py`); separately, salted SHA-256 identifier hashing for purge / reverse-pii | | A.8.25 Secure development life cycle | FL-helps | `docs/standards/code-review.md`, `release.md`, CI gates | | A.8.26 Application security requirements | FL-helps | F-compliance-110 strict gate; ReDoS guard | | A.8.27 Secure system architecture and engineering principles | FL-helps | Append-only audit log architecture | diff --git a/docs/reference/purge_subcommand-tr.md b/docs/reference/purge_subcommand-tr.md index cf111861..6da12fa5 100644 --- a/docs/reference/purge_subcommand-tr.md +++ b/docs/reference/purge_subcommand-tr.md @@ -1,9 +1,9 @@ # `forgelm purge` — Subcommand Referansı -> **Hedef kitle:** Eğitim corpus'larına ve koşum-kapsamlı artefact'lara karşı GDPR Madde 17 silme taleplerini karşılayan ForgeLM operatörleri ve ortaya çıkan `data.erasure_*` audit zincirini doğrulayan denetçiler. +> **Hedef kitle:** Eğitim corpus'larına ve koşum-kapsamlı artefact'lara karşı GDPR Madde 17 silme taleplerinin corpus adımını yürüten ForgeLM operatörleri ve ortaya çıkan `data.erasure_*` audit zincirini doğrulayan denetçiler. > **Ayna:** [purge_subcommand.md](purge_subcommand.md) -`forgelm purge`, ForgeLM eğitim corpus'ları ve koşum artefact'ları için **GDPR Madde 17 silinme hakkı**'nın operatör-yüzlü uygulamasıdır (Phase 21). Bir satırı, bir koşumun staging dizinini veya bir koşumun compliance bundle'ını atomik olarak siler ve her adımı tamper-evident `audit_log.jsonl` zincirine kaydeder. +`forgelm purge`, bir **GDPR Madde 17 silmesinin** corpus ve koşum-artefact adımı için operatör-yüzlü araçtır (Phase 21); yanıtın geri kalanı ve eğitilmiş ağırlıklar sizin sürecinizde kalır. Bir corpus satırını atomik olarak siler; `--run-id` ile bir koşumun staging dizinini (`--kind staging`) veya adı run id'yi embed eden compliance dosyalarını (`--kind artefacts`, aşağıya bakın) siler; her adımı tamper-evident `audit_log.jsonl` zincirine kaydeder. Deployer akışı (DSAR ticket → CLI çağrısı → doğrulama) için bkz. [`../guides/gdpr_erasure.md`](../guides/gdpr_erasure-tr.md). Bu sayfa flag-başına, event-başına referanstır. @@ -11,11 +11,11 @@ Deployer akışı (DSAR ticket → CLI çağrısı → doğrulama) için bkz. [` ```text forgelm purge --row-id ID --corpus PATH [--row-matches {one,all}] - [--output-dir DIR] [--justification TEXT] [--dry-run] - [--output-format {text,json}] + [--output-dir DIR] [--config PATH] [--justification TEXT] + [--dry-run] [--output-format {text,json}] forgelm purge --run-id RUN_ID --kind {staging,artefacts} - --output-dir DIR [--justification TEXT] [--dry-run] + [--output-dir DIR] [--justification TEXT] [--dry-run] [--output-format {text,json}] forgelm purge --check-policy --config PATH [--output-dir DIR] @@ -47,16 +47,20 @@ JSONL eğitim corpus'undan `id` (veya `row_id`) field'ı ile tanımlanan tek bir | Kind | Hedef | |---|---| -| `staging` | `/final_model.staging./` (varsa legacy `final_model.staging/` da). | -| `artefacts` | Adı `` embed eden `/compliance/*.json` dosyaları. | +| `staging` | `/final_model.staging./`; legacy `final_model.staging/` yalnızca içerdiği `staging_run_id` dosyasının içeriği ``'ye eşitse. ForgeLM bu işaretçiyi yazmaz; işaretsiz bir legacy staging dizinini kendiniz silin. | +| `artefacts` | `/compliance/` altında doğrudan bulunan, adı ``'yi `_`, `-`, `.` ya da adın başı/sonu ile sınırlı olarak içeren her dosya (yalnızca `.json` değil). | -`--kind logs` **kasıtlı olarak yok**: audit log'lar append-only Madde 17(3)(b) kayıtlarıdır ve tool tarafından silinmez. +> **`--kind artefacts` standart compliance paketini bulamaz.** Trainer ve exporter sabit dosya adları yazar (`compliance_report.json`, `training_manifest.yaml`, `data_provenance.json`, `data_governance_report.json`; yapılandırıldığında `risk_assessment.json` / `annex_iv_metadata.json`) ve hiçbiri run id içermez. Normal bir çıktı dizininde komut bu yüzden hiçbir şey bulmaz, `data.erasure_failed` (`NoMatchingArtefacts`) yayar ve 1 ile çıkar; hiçbir şey silmez. Bu, gerçek bir run id için (`fg-…`) geçerlidir: sabit bir adda geçen genel bir token (örneğin `json` veya `report`) o dosyalarla eşleşir ve onları siler; bu yüzden her zaman tam run id'yi verin. Yalnızca adına run id'yi kendiniz yazdığınız dosyalara etki eder. Resolver düzeltilene kadar paketi depolama katmanınız üzerinden kaldırın. + +`--kind logs` **kasıtlı olarak yok**: audit log append-only'dir ve tool onu asla silmez. Veri sahibini adlandıran girdilerin tutulup tutulamayacağı veri sorumlusunun belgelenmiş kararıdır: Madde 17(1)'deki silme nedenlerinden biri geçerliyse girdiler yalnızca bir Madde 17(3) istisnasının kapsadığı yerde kalır (17(3)(b), yalnızca işlemenin veri sorumlusunun tabi olduğu AB veya üye devlet hukukundan doğan bir yasal yükümlülük ya da kamu yararına veya kamu otoritesinin kullanılmasına ilişkin bir görev için gerekli olduğu ölçüde; 17(3)(e) hukuki taleplerin ileri sürülmesini, kullanılmasını veya savunulmasını kapsar); hiçbir neden geçerli değilse silme gerekmez, ancak talebe yine de gecikmeksizin ve en geç bir ay içinde yanıt verilir; yanıtta gerekçe, şikâyet etme ve yargı yoluna başvurma imkânı bildirilir (Madde 12(4)). ### Retention-policy raporu (`--check-policy`) -Salt-okunur tarama. `` üzerinde dolaşır, her artefact'ın yaşını (kanonik: audit-log genesis `timestamp`'i; fallback: dosya sistemi `mtime`'ı, `age_source=mtime` flag'iyle) yüklü config'in `retention:` horizon'larıyla karşılaştırır ve yapısal bir ihlal listesi yayar. +Salt-okunur tarama. `` üzerinde dolaşır, her artefact'ın yaşını (kanonik: `final_model.staging./` dizini için koşumun ilk audit event'i, yoksa audit-log genesis `timestamp`'i; fallback: dosya sistemi `mtime`'ı, `age_source=mtime` flag'iyle) yüklü config'in `retention:` horizon'larıyla karşılaştırır ve yapısal bir ihlal listesi yayar. + +**Paylaşılan çıktı dizinleri.** `compliance/`, `data_audit_report.json` ve ham-belge dizinlerinin yaşı, dosyanın en son yazıldığı andan değil, audit log'un *ilk* event'inden sayılır. Birçok koşum için yeniden kullanılan bir dizinde bugün yazılmış bir paket de ufku aşmış olarak raporlanabilir: raporun listelediği bir şeyi silmeden önce dosyanın kendi tarihine bakın. -**Başarılı policy raporu her zaman 0 ile çıkar** (report-not-gate semantik, design §10 Q5). Config-load başarısızlıkları `EXIT_CONFIG_ERROR` (1) ile çıkar — eksik, okunamaz veya Pydantic doğrulamasından geçemeyen explicit `--config` non-zero olarak yüzeyselleşir; böylece operatör "loader başarısız" durumunu "ihlal yok" sanmaz. CI gate kuran operatörler `--output-format json` kullanıp `jq '.violations | length'`'e pipe eder. +**Başarılı policy raporu her zaman 0 ile çıkar** (report-not-gate semantik, design §10 Q5). Config-load başarısızlıkları `EXIT_CONFIG_ERROR` (1) ile çıkar — eksik, okunamaz veya Pydantic doğrulamasından geçemeyen explicit `--config` non-zero olarak yüzeyselleşir; böylece operatör "loader başarısız" durumunu "ihlal yok" sanmaz. CI gate kuran operatörler `--output-format json` kullanır ve raporu kendileri sınar, örneğin `set -o pipefail` altında `jq -e '.success == true and has("count") and .count == 0'`: `count`, hiçbir `retention:` bloğu yüklenmediğinde yoktur; böylece gate hiç kontrol edilmemiş bir dizinde geçmek yerine başarısız olur, ve `--output-dir` gerçek eğitim çıktı dizini olmalıdır (olmayan bir dizin `count: 0` raporlar). ## Salt çözümlemesi @@ -69,7 +73,7 @@ Per-output-dir salt `/.forgelm_audit_salt`'ta ilk kullanımda oluşt ## `forgelm reverse-pii` ile cross-tool digest korelasyonu -`forgelm purge --row-id ` ve `forgelm reverse-pii --query --salt-source per_dir` **aynı** ``'e (yani aynı `.forgelm_audit_salt`'ı tüketerek) çalıştırıldığında, `data.erasure_*` event'lerindeki `target_id` ile `data.access_request_query` event'indeki `query_hash` byte-byte aynıdır. Compliance reviewer böylece aynı veri sahibi için Madde 17 silme ve Madde 15 erişim taleplerini cleartext identifier'ı asla görmeden korele edebilir. `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir` ile pin'lendi. +`forgelm purge` `target_id` kaydeder (salt ile `--row-id` değerinin SHA-256'sı); `forgelm reverse-pii` ise `--query` değerinin aynı fonksiyonunu `query_hash` olarak kaydeder. İkisi yalnızca şu üç koşulun hepsi sağlandığında byte-byte aynıdır: değer aynı metindir (satırın `id` / `row_id` alanı ile sorgu aynı string olmalıdır; opak UUID satır id'leri bir e-posta sorgusuna asla eşit olmaz), iki komut da aynı `--output-dir`'i (aynı `.forgelm_audit_salt`'ı) kullanır ve `FORGELM_AUDIT_SECRET` ikisinde de aynı durumdadır (ayarsız ya da aynı değerle ayarlı; ayarlıysa purge `salt_source=env_var` kaydeder). Erişim talebini varsayılan düz-metin modunda çalıştırın: `--salt-source` gerekmez (varsayılan mod zaten eşleşen bir `query_hash` kaydeder ve birebir metni arar); `--salt-source` ile `reverse-pii` corpus *içinde* digest arayan bir hash-mask taraması çalıştırır, bu yüzden düz metin içeren bir corpus'ta kişi orada olsa bile sıfır eşleşme alırsınız. Id'ler farklıysa erişim talebini ilgili silme işlemine, `forgelm purge --justification`'a verdiğiniz ticket referansı üzerinden bağlayın. Compliance reviewer böylece aynı veri sahibi için Madde 17 silme ve Madde 15 erişim taleplerini cleartext identifier'ı zincirde saklamadan korele edebilir (düz-metin tarama çıktısı cleartext eşleşmeler içerebilir: onu kişisel veri gibi koruyun). `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir` ile pin'lendi. ## Yayılan audit event'leri @@ -82,7 +86,7 @@ Altı event de [`audit_event_catalog.md`](audit_event_catalog-tr.md)'deki ortak | `data.erasure_failed` | Disk operasyonu raise etti VEYA eşleşen satır/koşum yok VEYA çoklu-satır policy belirsizliği reddetti. | Tüm `requested` field'ları + `error_class`, `error_message` | | `data.erasure_warning_memorisation` | Row erasure × `--output-dir` altında doğrudan bir `final_model/` dizini mevcut (birebir ad; `--output-dir` varsayılan olarak corpus'un dizinidir, bu yüzden eğitim çıktı dizininizi verin). ForgeLM corpus'u hangi koşumların tükettiğini belirlemez. | Tüm `completed` field'ları + `affected_run_ids` | | `data.erasure_warning_synthetic_data_present` | Row erasure × `output_dir`'de `synthetic_data*.jsonl` mevcut. | Tüm `completed` field'ları + `synthetic_files` | -| `data.erasure_warning_external_copies` | Yüklü config boş-olmayan `webhook` block'u içeriyor; downstream tüketiciler bildirim almış olabilir. | Tüm `completed` field'ları + `webhook_targets` (redact'li URL'ler) | +| `data.erasure_warning_external_copies` | **Yalnız row modunda, `--config` verildiğinde.** Yüklü config boş-olmayan `webhook` block'u içeriyor; downstream tüketiciler bildirim almış olabilir. | Tüm `completed` field'ları + `webhook_targets` (redact'li URL'ler) | `forgelm verify-audit /audit_log.jsonl` herhangi bir sayıda erasure event'i sonrası zinciri doğrulamaya devam eder — tool yeni event'ler ekler, eskileri yeniden yazmaz. @@ -91,21 +95,23 @@ Altı event de [`audit_event_catalog.md`](audit_event_catalog-tr.md)'deki ortak | Kod | Anlamı | |---|---| | 0 | Başarı veya başarılı `--check-policy` raporu (report-not-gate semantik). | -| 1 | Config hatası: bilinmeyen `--row-id`, eksik `--corpus`, karşılıklı dışlayıcı flag kombinasyonu, çakışan `staging_ttl_days` değerleri veya eksik / okunamaz / Pydantic doğrulamasından geçemeyen `--check-policy --config `. | -| 2 | Runtime hatası: I/O başarısızlığı, izin reddedildi, atomic-rename başarısızlığı. | +| 1 | Config hatası: bilinmeyen `--row-id`, `--run-id` ile eşleşen staging dizini / compliance dosyası yok (standart sabit-adlı pakete karşı `--kind artefacts` dahil), eksik `--corpus` ya da `--corpus`'suz `--row-id`, eksik `--kind`, seçilmemiş mod veya eksik / okunamaz / Pydantic doğrulamasından geçemeyen `--check-policy --config `. | +| 2 | Runtime hatası: I/O başarısızlığı, izin reddedildi, atomic-rename başarısızlığı; ayrıca argüman-parser kullanım hatası (`--row-id` ile `--run-id` gibi birlikte verilen iki mod, `--kind logs`), JSON zarfı yerine usage basar. | `--check-policy` asla 3 veya 4 dönmez. Kod 3 (`EXIT_EVAL_FAILURE`) ve 4 (`EXIT_AWAITING_APPROVAL`) eğitim pipeline'ı için ayrılmıştır ve bu subcommand'ın yüzeyinin parçası değildir. ## JSON çıktı zarfı -`--output-format json` ile her çağrı stdout'a tam olarak bir JSON nesnesi yazdırır. Şekil moda göre değişir — toplam üç zarf. +`--output-format json` ile argüman ayrıştırmayı geçen her çağrı stdout'a tam olarak bir JSON nesnesi yazdırır (argüman-parser hatası bunun yerine usage basar ve 2 ile çıkar). Başarılı ya da başarısız her zarf boolean bir `success` taşır; ona göre dallanın. Şekil moda göre değişir. **Row modu (başarı)** — `_perform_row_erasure_and_audit`'den yayılır: ```json { + "success": true, "mode": "row", "dry_run": false, + "row_id_hash": "01aeebf6566c0f6ec4a95a4678a2eb3ef45022ffb7bd9d5662b706d6d6506efc", "salt_source": "per_dir", "corpus_path": "/abs/path/data/train.jsonl", "matches": 1, @@ -115,22 +121,23 @@ Altı event de [`audit_event_catalog.md`](audit_event_catalog-tr.md)'deki ortak } ``` -`--dry-run` çağrılarında aynı zarf `dry_run: true` ile ve `bytes_freed` olmadan görünür (rewrite hiç olmaz). +`row_id_hash`, salt ile `--row-id` değerinin tam SHA-256 hex digest'idir; `data.erasure_*` event'leri aynı tam digest'i `target_id` olarak taşır (metin çıktısı yalnızca ilk 16 hex hanesini gösterir). `--dry-run` çağrılarında aynı zarf `dry_run: true` ile ve `bytes_freed` olmadan görünür (rewrite hiç olmaz). **Run modu (başarı)** — `_run_purge_run_id`'den yayılır: ```json { + "success": true, "mode": "run", "kind": "staging", "dry_run": false, "run_id": "fg-abc123def456", - "deleted": ["/abs/path/outputs/run42/final_model.staging.fg-abc123def456"], - "bytes_freed": 102400000 + "deleted": ["/final_model.staging.fg-abc123def456"], + "bytes_freed": 2048 } ``` -`deleted` gerçekten silinen mutlak yolların **listesidir** (ya da `--dry-run`'da `would_delete` anahtarı aynı şekli taşır ve `deleted` yoktur). +`deleted` gerçekten silinen yolların **listesidir**; yollar `--output-dir`'e verdiğiniz biçimde yazılır (ya da `--dry-run`'da `would_delete` anahtarı aynı şekli taşır ve `deleted` yoktur). **`--check-policy` modu** — `_run_purge_check_policy`'ten yayılır: @@ -139,29 +146,36 @@ Altı event de [`audit_event_catalog.md`](audit_event_catalog-tr.md)'deki ortak "success": true, "violations": [ { - "artefact_kind": "ephemeral_artefact", - "path": "/abs/path/outputs/run42/compliance/data_audit_report.json", + "artefact_kind": "compliance_bundle", + "path": "/compliance", "age_days": 121.4, "horizon_days": 90, - "age_source": "audit_genesis" + "age_source": "audit" + }, + { + "artefact_kind": "staging_dir[fg-old]", + "path": "/final_model.staging.fg-old", + "age_days": 121.4, + "horizon_days": 7, + "age_source": "audit" } ], - "count": 1 + "count": 2 } ``` -`age_source` ∈ `{audit_genesis, mtime}` — `audit_genesis` kanonik yaş (koşumun ilk audit event timestamp'i); `mtime` operatörün farkındalığı için işaretlenmiş filesystem fallback'i. +`artefact_kind` şunlardan biridir: `audit_log`, `staging_dir`, `staging_dir[]`, `compliance_bundle`, `data_audit_report` veya `raw_documents[]`. `age_source` ∈ `{audit, mtime}` — `audit` yaşın audit log'dan sayıldığı anlamına gelir (staging dizini için koşumun ilk event'i, yoksa log'un ilk event'i); `mtime` operatörün farkındalığı için işaretlenmiş filesystem fallback'idir. Hiçbir `retention:` bloğu yüklenmediğinde (`--config` yok ya da bloğu olmayan bir config) zarf `count`'suz `{"success": true, "violations": [], "note": "..."}` olur: hiçbir şey kontrol edilmemiştir. **Hata zarfı (her mod)** — `_output_error_and_exit`: ```json { "success": false, - "error": "Row id 'ali@example.com' not found in 'data/train.jsonl'." + "error": "No row with id= in 'data/train.jsonl'. Refusing to delete." } ``` -Asimetri notu: row/run başarı zarfları açık `success` field'ı **taşımaz** (`mode` ayraç başarı sinyalidir); `--check-policy` ve hata zarfı taşır. CI gate'leri `mode` anahtarına (başarı yolu) ya da `success: false`'a (hata yolu) dallanmalı; `success: true`'nin varlığı/yokluğuna değil. +Tanımlayıcı mesajda asla tekrarlanmaz: mesaj `--row-id` değeri yerine bir hash öneki taşır. ## Bkz. diff --git a/docs/reference/purge_subcommand.md b/docs/reference/purge_subcommand.md index 2c44b85f..0dcbc01a 100644 --- a/docs/reference/purge_subcommand.md +++ b/docs/reference/purge_subcommand.md @@ -1,9 +1,9 @@ # `forgelm purge` — Subcommand Reference -> **Audience:** ForgeLM operators honouring GDPR Article 17 erasure requests against training corpora and run-scoped artefacts, plus auditors verifying the resulting `data.erasure_*` audit chain. +> **Audience:** ForgeLM operators carrying out the corpus step of GDPR Article 17 erasure requests against training corpora and run-scoped artefacts, plus auditors verifying the resulting `data.erasure_*` audit chain. > **Mirror:** [purge_subcommand-tr.md](purge_subcommand-tr.md) -`forgelm purge` is the operator-facing implementation of the **GDPR Article 17 right-to-erasure** for ForgeLM training corpora and run artefacts (Phase 21). It atomically deletes a row, a run's staging directory, or a run's compliance bundle, and records every step in the tamper-evident `audit_log.jsonl` chain. +`forgelm purge` is the operator-facing tool for the corpus and run-artefact step of a **GDPR Article 17 erasure** (Phase 21); the rest of the response, and the trained weights, stay your process. It atomically erases a corpus row; with `--run-id` it deletes a run's staging directory (`--kind staging`) or compliance files whose names embed the run id (`--kind artefacts`, see below); and it records every step in the tamper-evident `audit_log.jsonl` chain. For the deployer-flow walkthrough (DSAR ticket → CLI invocation → verification), see [`../guides/gdpr_erasure.md`](../guides/gdpr_erasure.md). This page is the per-flag, per-event reference. @@ -11,11 +11,11 @@ For the deployer-flow walkthrough (DSAR ticket → CLI invocation → verificati ```text forgelm purge --row-id ID --corpus PATH [--row-matches {one,all}] - [--output-dir DIR] [--justification TEXT] [--dry-run] - [--output-format {text,json}] + [--output-dir DIR] [--config PATH] [--justification TEXT] + [--dry-run] [--output-format {text,json}] forgelm purge --run-id RUN_ID --kind {staging,artefacts} - --output-dir DIR [--justification TEXT] [--dry-run] + [--output-dir DIR] [--justification TEXT] [--dry-run] [--output-format {text,json}] forgelm purge --check-policy --config PATH [--output-dir DIR] @@ -47,16 +47,20 @@ Erases a single row identified by its `id` (or `row_id`) field from a JSONL trai | Kind | Target | |---|---| -| `staging` | `/final_model.staging./` (and the legacy `final_model.staging/` if present). | -| `artefacts` | `/compliance/*.json` files whose name embeds ``. | +| `staging` | `/final_model.staging./`, and the legacy `final_model.staging/` only when it contains a `staging_run_id` file whose content equals ``. ForgeLM does not write that marker, so remove an unmarked legacy staging directory yourself. | +| `artefacts` | Any file directly under `/compliance/` whose name contains `` bounded by `_`, `-`, `.` or the start/end of the name (not only `.json`). | -`--kind logs` is **intentionally absent**: audit logs are append-only Article 17(3)(b) records and are not deleted by the tool. +> **`--kind artefacts` does not find the standard compliance bundle.** The trainer and exporter write fixed basenames (`compliance_report.json`, `training_manifest.yaml`, `data_provenance.json`, `data_governance_report.json`, and `risk_assessment.json` / `annex_iv_metadata.json` when configured), none of which contains a run id. On a normal output directory the command therefore finds nothing, emits `data.erasure_failed` (`NoMatchingArtefacts`) and exits 1; it deletes nothing. That holds for a real run id (`fg-…`): a generic token that appears in a fixed name, such as `json` or `report`, does match those files and deletes them, so always pass the full run id. It acts only on files you have named with the run id yourself. Until the resolver is fixed, remove the bundle through your storage layer. + +`--kind logs` is **intentionally absent**: the audit log is append-only and the tool never deletes it. Whether entries that name a data subject may be kept is the controller's documented decision: if an Article 17(1) ground applies they stay only where an Article 17(3) exception covers them (17(3)(b) only to the extent processing is necessary for a legal obligation under Union or Member State law to which the controller is subject, or for a task in the public interest or in the exercise of official authority; 17(3)(e) covers the establishment, exercise or defence of legal claims); if no ground applies, erasure is not owed, but the request still gets an answer without delay and at the latest within one month, giving the reasons and the possibility of complaining and seeking a judicial remedy (Article 12(4)). ### Retention-policy report (`--check-policy`) -Read-only scan. Walks ``, compares each artefact's age (canonical: audit-log genesis `timestamp`; fallback: filesystem `mtime` with `age_source=mtime`) against the loaded config's `retention:` horizons, and emits a structured violation list. +Read-only scan. Walks ``, compares each artefact's age (canonical: the run's first audit event for a `final_model.staging./` directory, else the audit log's genesis `timestamp`; fallback: filesystem `mtime` with `age_source=mtime`) against the loaded config's `retention:` horizons, and emits a structured violation list. + +**Shared output directories.** `compliance/`, `data_audit_report.json` and the raw-document directories are aged from the audit log's *first* event, not from when the file was last written. In a directory reused for many runs, a bundle written today can be reported as past its horizon: check the file's own date before deleting anything the report lists. -**Successful policy reports always exit 0** (report-not-gate semantic per design §10 Q5). Config-load failures exit `EXIT_CONFIG_ERROR` (1) — an explicit `--config` that is missing, unreadable, or fails Pydantic validation surfaces as a non-zero so the operator does not mistake "loader failed" for "no violations". Operators wiring a CI gate use `--output-format json` and pipe to `jq '.violations | length'` themselves. +**Successful policy reports always exit 0** (report-not-gate semantic per design §10 Q5). Config-load failures exit `EXIT_CONFIG_ERROR` (1) — an explicit `--config` that is missing, unreadable, or fails Pydantic validation surfaces as a non-zero so the operator does not mistake "loader failed" for "no violations". Operators wiring a CI gate use `--output-format json` and test the report themselves, for example `jq -e '.success == true and has("count") and .count == 0'` under `set -o pipefail`: `count` is absent when no `retention:` block was loaded, so the gate fails instead of passing on a directory that was never checked, and `--output-dir` must be the real training output directory (a missing one reports `count: 0`). ## Salt resolution @@ -69,7 +73,7 @@ The per-output-dir salt at `/.forgelm_audit_salt` is created on firs ## Cross-tool digest correlation with `forgelm reverse-pii` -When `forgelm purge --row-id ` and `forgelm reverse-pii --query --salt-source per_dir` run against the **same** `` (i.e. consume the same `.forgelm_audit_salt`), the `target_id` field on `data.erasure_*` events and the `query_hash` field on `data.access_request_query` events are byte-identical. A compliance reviewer can therefore correlate Article 17 erasures and Article 15 access requests for the same data subject without ever seeing the cleartext identifier. Pinned by `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir`. +`forgelm purge` records `target_id` (SHA-256 of the salt plus the `--row-id` value) and `forgelm reverse-pii` records `query_hash` (the same function of the `--query` value). They are byte-identical only when all three hold: the value is the same text (a row's `id` / `row_id` and the query must be the same string; opaque UUID row ids never equal an e-mail query), both commands use the same `--output-dir` (the same `.forgelm_audit_salt`), and `FORGELM_AUDIT_SECRET` is in the same state for both (unset, or set to the same value; purge records `salt_source=env_var` when it is set). Run the access request in its default plaintext mode: `--salt-source` is not needed (the default mode already records a matching `query_hash`, and scans for the literal text); with it `reverse-pii` runs a hash-mask scan that looks for digests *inside the corpus*, so a plaintext corpus reports zero matches even when the person is there. When the ids differ, link a request to its erasure through the ticket reference you pass to `forgelm purge --justification`. A compliance reviewer can therefore correlate Article 17 erasures and Article 15 access requests for the same data subject without storing the cleartext identifier in the chain (the plaintext scan output can contain cleartext matches: protect it as personal data). Pinned by `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir`. ## Audit events emitted @@ -82,7 +86,7 @@ All six events ride the common envelope from [`audit_event_catalog.md`](audit_ev | `data.erasure_failed` | Disk operation raised, OR no matching row/run, OR multi-row policy refused on ambiguity. | All `requested` fields + `error_class`, `error_message` | | `data.erasure_warning_memorisation` | Row erasure × a `final_model/` directory exists directly under `--output-dir` (literal name; `--output-dir` defaults to the corpus's directory, so pass your training output directory). ForgeLM does not determine which runs consumed the corpus. | All `completed` fields + `affected_run_ids` | | `data.erasure_warning_synthetic_data_present` | Row erasure × `synthetic_data*.jsonl` exists in `output_dir`. | All `completed` fields + `synthetic_files` | -| `data.erasure_warning_external_copies` | Loaded config has a non-empty `webhook` block; downstream consumers may have received notices. | All `completed` fields + `webhook_targets` (redacted URLs) | +| `data.erasure_warning_external_copies` | **Row mode only, when `--config` is given.** Loaded config has a non-empty `webhook` block; downstream consumers may have received notices. | All `completed` fields + `webhook_targets` (redacted URLs) | `forgelm verify-audit /audit_log.jsonl` continues to validate the chain after any number of erasure events — the tool appends new events, never rewrites old ones. @@ -91,21 +95,23 @@ All six events ride the common envelope from [`audit_event_catalog.md`](audit_ev | Code | Meaning | |---|---| | 0 | Success, or a successful `--check-policy` report (report-not-gate semantic). | -| 1 | Config error: unknown `--row-id`, missing `--corpus`, mutually-exclusive flag combination, conflicting `staging_ttl_days` values, or `--check-policy --config ` that is missing / unreadable / fails Pydantic validation. | -| 2 | Runtime error: I/O failure, permission denied, atomic-rename failure. | +| 1 | Config error: unknown `--row-id`, no staging directory / compliance file matching `--run-id` (including `--kind artefacts` against the standard fixed-name bundle), a missing `--corpus` or `--row-id` without `--corpus`, a missing `--kind`, no mode selected, or `--check-policy --config ` that is missing / unreadable / fails Pydantic validation. | +| 2 | Runtime error: I/O failure, permission denied, atomic-rename failure; also an argument-parser usage error (two modes together such as `--row-id` with `--run-id`, `--kind logs`), which prints usage instead of a JSON envelope. | `--check-policy` never returns code 3 or 4. Codes 3 (`EXIT_EVAL_FAILURE`) and 4 (`EXIT_AWAITING_APPROVAL`) are reserved for the training pipeline and are not part of this subcommand's surface. ## JSON output envelope -With `--output-format json` every invocation prints exactly one JSON object on stdout. The shape varies by mode — three envelopes in total. +With `--output-format json` every invocation that gets past argument parsing prints exactly one JSON object on stdout (an argument-parser error prints usage and exits 2 instead). Every envelope, success or failure, carries a boolean `success`; branch on it. The shape varies by mode. **Row mode (success)** — emitted by `_perform_row_erasure_and_audit`: ```json { + "success": true, "mode": "row", "dry_run": false, + "row_id_hash": "01aeebf6566c0f6ec4a95a4678a2eb3ef45022ffb7bd9d5662b706d6d6506efc", "salt_source": "per_dir", "corpus_path": "/abs/path/data/train.jsonl", "matches": 1, @@ -115,22 +121,23 @@ With `--output-format json` every invocation prints exactly one JSON object on s } ``` -For `--dry-run` invocations the same envelope appears with `dry_run: true` and without `bytes_freed` (the rewrite never happens). +`row_id_hash` is the full SHA-256 hex digest of the salt plus the `--row-id` value; the `data.erasure_*` events carry the same full digest as `target_id` (the text output shows only its first 16 hex digits). For `--dry-run` invocations the same envelope appears with `dry_run: true` and without `bytes_freed` (the rewrite never happens). **Run mode (success)** — emitted by `_run_purge_run_id`: ```json { + "success": true, "mode": "run", "kind": "staging", "dry_run": false, "run_id": "fg-abc123def456", - "deleted": ["/abs/path/outputs/run42/final_model.staging.fg-abc123def456"], - "bytes_freed": 102400000 + "deleted": ["/final_model.staging.fg-abc123def456"], + "bytes_freed": 2048 } ``` -`deleted` is a **list** of absolute paths actually removed (or, on `--dry-run`, the `would_delete` key carries the same shape and `deleted` is absent). +`deleted` is a **list** of the paths actually removed, spelled relative to `--output-dir` as you passed it (or, on `--dry-run`, the `would_delete` key carries the same shape and `deleted` is absent). **`--check-policy` mode** — emitted by `_run_purge_check_policy`: @@ -139,29 +146,36 @@ For `--dry-run` invocations the same envelope appears with `dry_run: true` and w "success": true, "violations": [ { - "artefact_kind": "ephemeral_artefact", - "path": "/abs/path/outputs/run42/compliance/data_audit_report.json", + "artefact_kind": "compliance_bundle", + "path": "/compliance", "age_days": 121.4, "horizon_days": 90, - "age_source": "audit_genesis" + "age_source": "audit" + }, + { + "artefact_kind": "staging_dir[fg-old]", + "path": "/final_model.staging.fg-old", + "age_days": 121.4, + "horizon_days": 7, + "age_source": "audit" } ], - "count": 1 + "count": 2 } ``` -`age_source` ∈ `{audit_genesis, mtime}` — `audit_genesis` is the canonical age (run's first audit event timestamp); `mtime` is the filesystem fallback flagged for the operator's awareness. +`artefact_kind` is one of `audit_log`, `staging_dir`, `staging_dir[]`, `compliance_bundle`, `data_audit_report` or `raw_documents[]`. `age_source` ∈ `{audit, mtime}` — `audit` is the age counted from the audit log (the run's first event for a staging directory, else the log's first event); `mtime` is the filesystem fallback flagged for the operator's awareness. When no `retention:` block was loaded (no `--config`, or a config without one) the envelope is `{"success": true, "violations": [], "note": "..."}` with no `count`: nothing was checked. **Failure envelope (any mode)** — `_output_error_and_exit`: ```json { "success": false, - "error": "Row id 'ali@example.com' not found in 'data/train.jsonl'." + "error": "No row with id= in 'data/train.jsonl'. Refusing to delete." } ``` -Note the asymmetry: row/run success envelopes do **not** carry an explicit `success` field (the `mode` discriminator is the success signal); `--check-policy` and the failure envelope do. Operators wiring CI gates should branch on the `mode` key (success path) or `success: false` (failure path), not on the presence/absence of `success: true`. +The identifier is never echoed: the message carries a hash prefix instead of the `--row-id` value. ## See also diff --git a/docs/reference/reverse_pii_subcommand-tr.md b/docs/reference/reverse_pii_subcommand-tr.md index 55cc2c5e..6316267a 100644 --- a/docs/reference/reverse_pii_subcommand-tr.md +++ b/docs/reference/reverse_pii_subcommand-tr.md @@ -1,9 +1,9 @@ # `forgelm reverse-pii` — Subcommand Referansı -> **Hedef kitle:** Eğitim corpus'larına karşı GDPR Madde 15 erişim hakkı taleplerini yanıtlayan ForgeLM operatörleri ve ortaya çıkan `data.access_request_query` audit satırını doğrulayan denetçiler. +> **Hedef kitle:** Eğitim corpus'larına karşı GDPR Madde 15 erişim yanıtları hazırlayan ForgeLM operatörleri ve ortaya çıkan `data.access_request_query` audit satırını doğrulayan denetçiler. > **Ayna:** [reverse_pii_subcommand.md](reverse_pii_subcommand.md) -`forgelm reverse-pii`, ForgeLM eğitim corpus'ları için **GDPR Madde 15 erişim hakkı**'nın operatör-yüzlü uygulamasıdır (Phase 38). [`forgelm purge`](purge_subcommand-tr.md) "satırımı sil" derken, `reverse-pii` "identifier'ımın geçtiği her satırı bul" der. +`forgelm reverse-pii`, ForgeLM eğitim corpus'ları için bir **GDPR Madde 15 erişim yanıtının** girdisidir (Phase 38): bir identifier'ın nerede geçtiğini bulur; sıfır eşleşme yalnızca literal metnin taradığınız dosyalarda olmadığını söyler. [`forgelm purge`](purge_subcommand-tr.md) "satırımı sil" derken, `reverse-pii` "identifier'ımın geçtiği her satırı bul" der. ## Synopsis @@ -23,7 +23,7 @@ forgelm reverse-pii --query VALUE | `--type {...}` | hayır (varsayılan `literal`) | Identifier kategorisi. `literal` ve tip-spesifik değerler (`email`, `phone`, `tr_id`, `us_ssn`, `iban`, `credit_card`) `--query`'yi literal substring olarak işler (`re.escape` uygulanır) — Madde 15 erişim talepleri için güvenli seçim, e-postalardaki noktalar keyfi karakterlere eşleşmemeli. `custom` `--query`'yi keyfi Python regex olarak yorumlar (dikkatli kullan; POSIX main-thread çağrılarında dosya başına SIGALRM timeout ile ReDoS guard'lı). | | `--salt-source {per_dir,env_var}` | hayır | **Hash-mask scan**'a geçiş: `forgelm purge`'ün kullandığı aynı per-output-dir salt ile `SHA256(salt + identifier)` hesaplanır ve her JSONL satırında aranır. Bu flag olmadan, scan plaintext residual scan'dir. | | `--output-dir DIR` | hayır | Per-output-dir salt dosyasını (`.forgelm_audit_salt`) içeren dizin. Salt buradan HEM audit-event hash'i için (her çağrı, plaintext veya hash-mask mod) HEM de `--salt-source` set olduğunda hash-mask scan için okunur/oluşturulur. İlk çözülen corpus dosyasının parent'ına default'lar; implicit fallback çözülen dizini adlandıran bir WARNING yayar; böylece operatör `forgelm purge` ile cross-tool korelasyon için `--output-dir`'i pin'leyebilir. | -| `--audit-dir DIR` | hayır | Audit chain entry'lerinin yazılacağı yer (default: `--output-dir` ile aynı; `forgelm verify-audit` aynı subject için Madde 17 + Madde 15 event'lerini tek zincirde korele eder). Yazılamaz bir explicit `--audit-dir` Madde 15 forensik kaydını sessizce düşürmek yerine yüksek sesle başarısız olur. | +| `--audit-dir DIR` | hayır | Audit chain entry'lerinin yazılacağı yer (default: `--output-dir` ile aynı; `forgelm verify-audit` aynı veri sahibi için Madde 17 + Madde 15 event'lerini tek zincirde korele eder). Yazılamaz bir explicit `--audit-dir` Madde 15 forensik kaydını sessizce düşürmek yerine yüksek sesle başarısız olur. | ## İki scan modu @@ -47,7 +47,7 @@ $ forgelm reverse-pii --query "alice@example.com" --type email \ --salt-source per_dir --output-dir ./outputs data/*.jsonl ``` -`--salt-source env_var` `FORGELM_AUDIT_SECRET`'in set olmasını gerektirir; `per_dir` `/.forgelm_audit_salt`'taki salt dosyasını okur. +`--salt-source env_var` `FORGELM_AUDIT_SECRET`'in set olmasını gerektirir; `per_dir` `/.forgelm_audit_salt`'taki salt dosyasını okur. `forgelm ingest --pii-mask` digest değil `[REDACTED]` yazar, bu yüzden bu mod ForgeLM'in kendi maskelediği bir corpus'ta hiçbir şey bulmaz; onlar için varsayılan düz-metin taramasını kullanın. ## Identifier tipleri @@ -59,7 +59,7 @@ $ forgelm reverse-pii --query "alice@example.com" --type email \ ## `forgelm purge` ile cross-tool digest korelasyonu -`forgelm reverse-pii --query --salt-source per_dir` ve `forgelm purge --row-id ` **aynı** ``'e (yani aynı `.forgelm_audit_salt`'ı tüketerek) çalıştırıldığında, `data.access_request_query` üzerindeki `query_hash` ile `data.erasure_*` event'leri üzerindeki `target_id` byte-byte aynıdır. Bu, compliance reviewer'ın Madde 15 erişim talebini ve aynı subject için Madde 17 silmesini tek bir audit zincirinde, cleartext identifier operatör terminalinden hiç çıkmadan korele etmesini sağlar. `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir` ile pin'lendi. +`forgelm purge` `target_id` kaydeder (salt ile `--row-id` değerinin SHA-256'sı); `forgelm reverse-pii` ise `--query` değerinin aynı fonksiyonunu `query_hash` olarak kaydeder. İkisi yalnızca şu üç koşulun hepsi sağlandığında byte-byte aynıdır: değer aynı metindir (satırın `id` / `row_id` alanı ile sorgu aynı string olmalıdır; opak UUID satır id'leri bir e-posta sorgusuna asla eşit olmaz), iki komut da aynı `--output-dir`'i (aynı `.forgelm_audit_salt`'ı) kullanır ve `FORGELM_AUDIT_SECRET` ikisinde de aynı durumdadır (ayarsız ya da aynı değerle ayarlı; ayarlıysa purge `salt_source=env_var` kaydeder). Erişim talebini varsayılan düz-metin modunda çalıştırın: `--salt-source` gerekmez (varsayılan mod zaten eşleşen bir `query_hash` kaydeder ve birebir metni arar); `--salt-source` ile `reverse-pii` corpus *içinde* digest arayan bir hash-mask taraması çalıştırır, bu yüzden düz metin içeren bir corpus'ta kişi orada olsa bile sıfır eşleşme alırsınız. Id'ler farklıysa erişim talebini ilgili silme işlemine, `forgelm purge --justification`'a verdiğiniz ticket referansı üzerinden bağlayın. Bu, compliance reviewer'ın Madde 15 erişim talebini ve aynı veri sahibi için Madde 17 silmesini tek bir audit zincirinde, cleartext identifier'ı o zincirde saklamadan korele etmesini sağlar. Düz-metin tarama çıktısı cleartext eşleşmeler içerebilir: stdout'u, yönlendirilmiş dosyaları ve yakalanmış log'ları kişisel veri gibi koruyun. `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir` ile pin'lendi. `/.forgelm_audit_salt`'taki salt **yalnızca identifier-hash salt'ıdır** — audit-chain HMAC anahtarına katılmaz; o anahtar bağımsız olarak `forgelm.compliance.AuditLogger.__init__` içinde `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` şeklinde türetilir. @@ -71,7 +71,7 @@ Her çağrı tam olarak bir event yayar ([katalog satırı](audit_event_catalog- |---|---|---| | `data.access_request_query` | Scan tamamlandıktan sonra (veya mid-scan I/O hatası sonrası — `error_class` / `error_message` ile). | `query_hash` (raw identifier'ın salt'lı SHA-256'sı — asla raw; purge'ün per-output-dir salt'ını yeniden kullanır), `identifier_type` ∈ `{literal, email, phone, tr_id, us_ssn, iban, credit_card, custom}`, `scan_mode` ∈ `{plaintext, hash}`, `salt_source` ∈ `{plaintext, per_dir, env_var}`, `files_scanned` (path'ler), `match_count`, opsiyonel `error_class` / `error_message` | -Chain satırı **hash'li** identifier'ı kaydeder; böylece zincirin kendisi subject verisini sızdırmaz. +Chain satırı **hash'li** identifier'ı kaydeder; böylece zincir onu açık halde tutmaz. Digest anonim değil, kişisel veridir: salt dosyası log'un yanında durur; ikisini de okuyabilen biri tahmin ettiği bir identifier'ı onunla sınayabilir (salt ayrı ve kendi kontrolleri altında tutulmuyorsa Madde 4(5) anlamında takma adlandırma da sayılmaz). Digest'leri kişisel veri sayın. ## Exit kodları diff --git a/docs/reference/reverse_pii_subcommand.md b/docs/reference/reverse_pii_subcommand.md index 97c9b638..47989bed 100644 --- a/docs/reference/reverse_pii_subcommand.md +++ b/docs/reference/reverse_pii_subcommand.md @@ -1,9 +1,9 @@ # `forgelm reverse-pii` — Subcommand Reference -> **Audience:** ForgeLM operators answering GDPR Article 15 right-of-access requests against training corpora, plus auditors verifying the resulting `data.access_request_query` audit row. +> **Audience:** ForgeLM operators preparing GDPR Article 15 access responses against training corpora, plus auditors verifying the resulting `data.access_request_query` audit row. > **Mirror:** [reverse_pii_subcommand-tr.md](reverse_pii_subcommand-tr.md) -`forgelm reverse-pii` is the operator-facing implementation of the **GDPR Article 15 right of access** for ForgeLM training corpora (Phase 38). Where [`forgelm purge`](purge_subcommand.md) answers "delete my row", `reverse-pii` answers "find every line where my identifier appears". +`forgelm reverse-pii` is an input to a **GDPR Article 15 access response** for ForgeLM training corpora (Phase 38): it finds where an identifier appears, and a zero-match scan only says the literal text is absent from the files you scanned. Where [`forgelm purge`](purge_subcommand.md) answers "delete my row", `reverse-pii` answers "find every line where my identifier appears". ## Synopsis @@ -47,7 +47,7 @@ $ forgelm reverse-pii --query "alice@example.com" --type email \ --salt-source per_dir --output-dir ./outputs data/*.jsonl ``` -`--salt-source env_var` requires `FORGELM_AUDIT_SECRET` to be set; `per_dir` reads the salt file at `/.forgelm_audit_salt`. +`--salt-source env_var` requires `FORGELM_AUDIT_SECRET` to be set; `per_dir` reads the salt file at `/.forgelm_audit_salt`. `forgelm ingest --pii-mask` writes `[REDACTED]`, not digests, so this mode finds nothing in a corpus ForgeLM masked itself; use the default plaintext scan for those. ## Identifier types @@ -59,7 +59,7 @@ $ forgelm reverse-pii --query "alice@example.com" --type email \ ## Cross-tool digest correlation with `forgelm purge` -When `forgelm reverse-pii --query --salt-source per_dir` and `forgelm purge --row-id ` run against the **same** `` (i.e. consume the same `.forgelm_audit_salt`), the `query_hash` field on `data.access_request_query` and the `target_id` field on `data.erasure_*` events are byte-identical. This lets a compliance reviewer correlate the Article 15 access request and the Article 17 erasure for the same data subject in a single audit chain, without the cleartext identifier ever leaving the operator's terminal. Pinned by `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir`. +`forgelm purge` records `target_id` (SHA-256 of the salt plus the `--row-id` value) and `forgelm reverse-pii` records `query_hash` (the same function of the `--query` value). They are byte-identical only when all three hold: the value is the same text (a row's `id` / `row_id` and the query must be the same string; opaque UUID row ids never equal an e-mail query), both commands use the same `--output-dir` (the same `.forgelm_audit_salt`), and `FORGELM_AUDIT_SECRET` is in the same state for both (unset, or set to the same value; purge records `salt_source=env_var` when it is set). Run the access request in its default plaintext mode: `--salt-source` is not needed (the default mode already records a matching `query_hash`, and scans for the literal text); with it `reverse-pii` runs a hash-mask scan that looks for digests *inside the corpus*, so a plaintext corpus reports zero matches even when the person is there. When the ids differ, link a request to its erasure through the ticket reference you pass to `forgelm purge --justification`. This lets a compliance reviewer correlate the Article 15 access request and the Article 17 erasure for the same data subject in a single audit chain without storing the cleartext identifier in that chain. The plaintext scan output can contain cleartext matches: protect stdout, redirected files and captured logs as personal data. Pinned by `tests/test_reverse_pii.py::test_purge_target_id_matches_reverse_pii_query_hash_on_same_output_dir`. The salt at `/.forgelm_audit_salt` is the **identifier-hash salt only** — it does NOT participate in the audit-chain HMAC key, which is derived independently as `SHA-256(FORGELM_AUDIT_SECRET ‖ run_id)` inside `forgelm.compliance.AuditLogger.__init__`. @@ -71,7 +71,7 @@ Every invocation emits exactly one event ([catalog row](audit_event_catalog.md#a |---|---|---| | `data.access_request_query` | After the scan completes (or after a mid-scan I/O failure — with `error_class` / `error_message`). | `query_hash` (salted SHA-256 of raw identifier — never raw; reuses purge's per-output-dir salt), `identifier_type` ∈ `{literal, email, phone, tr_id, us_ssn, iban, credit_card, custom}`, `scan_mode` ∈ `{plaintext, hash}`, `salt_source` ∈ `{plaintext, per_dir, env_var}`, `files_scanned` (paths), `match_count`, optional `error_class` / `error_message` | -The chain row records the **hashed** identifier so the chain itself does not leak the subject's data. +The chain row records the **hashed** identifier so the chain does not store it in clear. The digest is personal data, not anonymous data: the salt file sits next to the log, so whoever can read both can test a guessed identifier against it (it is pseudonymisation under Article 4(5) only if the salt is kept separately under its own controls). Treat the digests as personal data. ## Exit codes diff --git a/docs/reference/usage-tr.md b/docs/reference/usage-tr.md index 7bd9fa34..34ada6ba 100644 --- a/docs/reference/usage-tr.md +++ b/docs/reference/usage-tr.md @@ -170,7 +170,7 @@ forgelm ingest ./scan.pdf --strategy sliding --chunk-size 1024 --overlap 128 \ --output data/scan.jsonl # Yazmadan önce PII'yi maskele -forgelm ingest ./customer_emails/ --pii-mask --output data/anon.jsonl +forgelm ingest ./customer_emails/ --pii-mask --output data/masked.jsonl # Token-aware chunking — chunk'ları modelinizin vocab'ına göre boyutlandırır forgelm ingest ./policies/ --recursive --output data/policies.jsonl \ diff --git a/docs/reference/usage.md b/docs/reference/usage.md index 4b9f1187..9fdd750d 100644 --- a/docs/reference/usage.md +++ b/docs/reference/usage.md @@ -187,7 +187,7 @@ forgelm ingest ./scan.pdf --strategy sliding --chunk-size 1024 --overlap 128 \ --output data/scan.jsonl # Mask PII before writing -forgelm ingest ./customer_emails/ --pii-mask --output data/anon.jsonl +forgelm ingest ./customer_emails/ --pii-mask --output data/masked.jsonl # Token-aware chunking — sizes chunks against your model's vocab forgelm ingest ./policies/ --recursive --output data/policies.jsonl \ diff --git a/docs/roadmap/phase-16-trust-surface-hardening.md b/docs/roadmap/phase-16-trust-surface-hardening.md index c3b719f2..6b1d5441 100644 --- a/docs/roadmap/phase-16-trust-surface-hardening.md +++ b/docs/roadmap/phase-16-trust-surface-hardening.md @@ -760,6 +760,12 @@ committed, then the same with Sonnet, then the next step. that would have caught it**, and they share one hard constraint: EN, TR and all six site locales change in the same commit. + *Landed with the GDPR-claims fix:* `check_usermanual_schema_drift.py` now + also fails on a fenced-YAML top-level block `ForgeConfig` does not have + (the `chat:` / `output:` / `gdpr.md` blocks), with a reasoned exemption + table and stale-row detection. Still open here: the inline-code + config-key-in-prose half, and the two known-open blocks the guard lists. + Regulation (EU) 2026/1744 moves Annex III high-risk obligations to **2 December 2027** and Annex I to **2 August 2028**; five current surfaces still tie "full enforcement" to 2 August 2026, and the original wording diff --git a/docs/standards/documentation.md b/docs/standards/documentation.md index a75f267d..30d5b6ab 100644 --- a/docs/standards/documentation.md +++ b/docs/standards/documentation.md @@ -230,7 +230,7 @@ See [localization.md](localization.md) for the full rule. Summary: - User-facing docs in `docs/` root, `docs/reference/`, `docs/roadmap.md`, `docs/qms/`, and `docs/usermanuals/{en,tr}/` have `-tr.md` (or `tr/`-tree) mirrors. - Internal docs (`standards/`, `marketing/`, `analysis/`, `design/`) are English only. -- Mirrors must stay in **H2 + H3 + H4 structural sync** (Wave 3 / Faz 24 formalisation): every heading at depth 2-4 in the EN file must have a matching same-position heading in the TR file. Cosmetic wording differences inside a section are fine; structural divergence (sections added in one, missing in other; reordered headings) is a bug. The `tools/check_bilingual_parity.py --strict` gate fails CI on any mismatch — its scope covers every hand-registered EN/TR pair (across `docs/guides/`, `docs/qms/`, `docs/reference/`) plus the auto-discovered `docs/usermanuals/` pairs; the pair count self-updates as mirrors are added, so it is not pinned here. The companion `tools/check_bilingual_code_blocks.py --strict` gate additionally enforces matching fenced-block counts and per-block YAML-key sets across the same pairs, and `tools/check_usermanual_schema_drift.py --strict` validates that every fenced YAML key under `docs/usermanuals/` resolves against the real `ForgeConfig` schema. +- Mirrors must stay in **H2 + H3 + H4 structural sync** (Wave 3 / Faz 24 formalisation): every heading at depth 2-4 in the EN file must have a matching same-position heading in the TR file. Cosmetic wording differences inside a section are fine; structural divergence (sections added in one, missing in other; reordered headings) is a bug. The `tools/check_bilingual_parity.py --strict` gate fails CI on any mismatch — its scope covers every hand-registered EN/TR pair (across `docs/guides/`, `docs/qms/`, `docs/reference/`) plus the auto-discovered `docs/usermanuals/` pairs; the pair count self-updates as mirrors are added, so it is not pinned here. The companion `tools/check_bilingual_code_blocks.py --strict` gate additionally enforces matching fenced-block counts and per-block YAML-key sets across the same pairs, and `tools/check_usermanual_schema_drift.py --strict` fails when a fenced YAML block under `docs/usermanuals/` cannot be read, when a fragment uses a key path the real `ForgeConfig` schema does not declare, and when any block uses a top-level key `ForgeConfig` rejects, unless the guard lists that block with a reason as another tool's YAML (a CI workflow, a compose file) or as a known-open block a tracked issue owns. ## Cross-linking from bilingual content to EN-only docs diff --git a/docs/standards/localization.md b/docs/standards/localization.md index bbac0955..53c565b9 100644 --- a/docs/standards/localization.md +++ b/docs/standards/localization.md @@ -137,6 +137,9 @@ For the parts we do translate, keep terminology consistent. A small glossary: | drift (model / data) | drift | Kept in English, as in the guides and the site. *Kayma* names a schema drift (*şema kayması*) or a threshold or preference shift | | metrics exporter | exporter | Kept in English, like *endpoint* and *webhook*; suffixes take an apostrophe (*exporter'ı*) | | alert / notification / warning | alarm / bildirim / uyarı | A monitoring or threshold alert (drift, cost) is *alarm*; a webhook's chat message is *bildirim*, even where English calls it an alert; a log warning is *uyarı* | +| data subject | veri sahibi | The GDPR term; where a page quotes KVKK (Law 6698) use the law's own *ilgili kişi* | +| controller / processor | veri sorumlusu / veri işleyen | Not *kontrolör* | +| salt (hash salt) / digest | salt / digest | Kept in English like *hash*; not *tuz* / *özet* | When in doubt, look at sibling TR docs and match the most frequent choice. Don't invent new terminology mid-series. diff --git a/docs/usermanuals/en/compliance/gdpr-erasure.md b/docs/usermanuals/en/compliance/gdpr-erasure.md index 359a8561..e928cec0 100644 --- a/docs/usermanuals/en/compliance/gdpr-erasure.md +++ b/docs/usermanuals/en/compliance/gdpr-erasure.md @@ -1,22 +1,22 @@ --- title: GDPR Erasure & Access -description: Honour Article 15 (right of access) and Article 17 (right to erasure) requests with the `forgelm purge` and `forgelm reverse-pii` subcommands. +description: Support Article 15 (access) and Article 17 (erasure) requests with the `forgelm purge` and `forgelm reverse-pii` subcommands. --- # GDPR Erasure & Access -ForgeLM ships sister subcommands for the two most-common GDPR data-subject rights: `forgelm purge` (Article 17 — right to erasure) and `forgelm reverse-pii` (Article 15 — right of access). They share a per-output-dir salt so a compliance reviewer can correlate erasures and access requests for the same data subject in a single tamper-evident audit chain — without the cleartext identifier ever leaving the operator's terminal. +ForgeLM ships sister subcommands that support two common GDPR data-subject requests: `forgelm purge` removes rows (one step of an Article 17 erasure) and `forgelm reverse-pii` finds rows (an input to an Article 15 response). Neither is a complete response on its own. They share a per-output-dir salt so a compliance reviewer can correlate erasures and access requests for the same data subject in a single tamper-evident audit chain — without the cleartext identifier ever leaving the operator's terminal. This page is the operator quick-reference. The deeper deployer-flow walkthrough lives in the [GDPR erasure deployer guide on GitHub](https://github.com/HodeTech/ForgeLM/blob/main/docs/guides/gdpr_erasure.md); per-flag references are in the in-manual [CLI Reference](#/reference/cli) and the dedicated subcommand pages [`purge_subcommand.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/reference/purge_subcommand.md) + [`reverse_pii_subcommand.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/reference/reverse_pii_subcommand.md) on GitHub. ## Two rights, one chain -| Right | Subcommand | What it answers | +| Request | Subcommand | What it does | |---|---|---| -| **Article 15** — right of access | `forgelm reverse-pii` | "Do you hold any record of me?" — scans JSONL corpora for the subject's identifier. | -| **Article 17** — right to erasure | `forgelm purge` | "Delete every record of me." — atomically removes a row, a staging directory, or a compliance bundle. | +| **Article 15** — access | `forgelm reverse-pii` | Finds the lines that contain the subject's identifier in the JSONL corpora you scan: an input to the response, not the response. | +| **Article 17** — erasure | `forgelm purge` | Atomically removes a corpus row by id (one step of an erasure, not all of it); with `--run-id` it removes a staging directory or compliance files whose names embed the run id. | -Both subcommands hash the subject's identifier with the **same** per-output-dir salt (`/.forgelm_audit_salt`) before recording the audit row. The `query_hash` field on `data.access_request_query` and the `target_id` field on `data.erasure_*` are byte-identical for the same subject, which lets a reviewer correlate the two without cleartext. +Both subcommands hash the subject's identifier with the **same** per-output-dir salt (`/.forgelm_audit_salt`) before recording the audit row. The `query_hash` field on `data.access_request_query` and the `target_id` field on `data.erasure_*` are byte-identical only when the `--row-id` value and the `--query` value are the same text, both commands use the same `--output-dir`, and `FORGELM_AUDIT_SECRET` is in the same state for both; otherwise link the two with the ticket reference you pass to `forgelm purge --justification`. ## Article 17 — `forgelm purge` @@ -29,7 +29,7 @@ forgelm purge --row-id "ali@example.com" --corpus train.jsonl \ # Run-scoped erasure forgelm purge --run-id fg-abc123def456 --kind staging --output-dir ./outputs -forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs +forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs # finds nothing on the standard bundle, see below # Retention-policy report (read-only) forgelm purge --check-policy --config configs/run.yaml --output-dir ./outputs @@ -44,13 +44,16 @@ Six events ride the audit chain (see the [Audit Event Catalog on GitHub](https:/ - `data.erasure_failed` — emitted instead when the disk operation raised, or no matching row/run was found. - `data.erasure_warning_memorisation` — corpus-row erasure × a `final_model/` exists directly under `--output-dir` (the row is gone from disk but may still be memorised in the trained weights). - `data.erasure_warning_synthetic_data_present` — corpus-row erasure × `synthetic_data*.jsonl` exists in `output_dir`. -- `data.erasure_warning_external_copies` — loaded config has a non-empty `webhook` block; downstream consumers may have received notices. +- `data.erasure_warning_external_copies` — corpus-row erasure only, and only when you pass `--config`: the config's `webhook` block is non-empty (a literal `url`, or a `url_env` variable exported in the purge environment); downstream consumers may have received notices. ### What it does NOT do - **Re-train models.** Removing a row from the corpus does not unmemorise it from already-trained weights — full retraining without the erased row is the only proper mitigation. The tool emits `data.erasure_warning_memorisation` so the gap is visible. -- **Delete the audit log.** Article 17(3)(b) preserves audit / accounting records as a legal-obligation defence; the tool records the erasure as a new event rather than rewriting history. -- **Push notices to downstream consumers.** Webhook receivers, dataset mirrors, and deployed model endpoints are the operator's runtime-layer responsibility (Article 17(2)). The `data.erasure_warning_external_copies` event is the explicit reminder. +- **Delete the audit log.** The tool records the erasure as a new event rather than rewriting history, and never deletes an audit-log entry. Whether entries that name the data subject may be kept is the controller's decision, and it has to be documented. If an Article 17(1) ground applies, the entries stay only where an Article 17(3) exception covers them: 17(3)(b) applies only to the extent processing is necessary for compliance with a legal obligation under Union or Member State law to which the controller is subject (or for a task in the public interest or in the exercise of official authority), and 17(3)(e) covers the establishment, exercise or defence of legal claims. If no Article 17(1) ground applies (the entries still being necessary for the purpose they were collected for rules out only 17(1)(a); also check consent withdrawal, an Article 21 objection and unlawful processing), erasure is not owed, but the request still gets an answer without delay and at the latest within one month of receipt, giving the reasons and the possibility of complaining to a supervisory authority and seeking a judicial remedy (Article 12(4)). +- **Remove an unmarked legacy `final_model.staging/`.** `--kind staging` deletes `final_model.staging./`, and the legacy `final_model.staging/` only when it holds a `staging_run_id` file equal to the run id. ForgeLM does not write that marker, so remove an unmarked legacy directory yourself. +- **Find the standard compliance bundle with `--kind artefacts`.** The trainer writes fixed basenames (`compliance_report.json`, `training_manifest.yaml`, ...) that contain no run id, and the resolver matches only filenames that embed it. On a normal output directory the command finds nothing, emits `data.erasure_failed` (`NoMatchingArtefacts`), exits 1 and deletes nothing. That holds for a real run id (`fg-…`): a generic token that appears in a fixed name, such as `json` or `report`, does match those files and deletes them, so always pass the full run id. Remove the bundle through your storage layer. +- **Push notices to downstream consumers.** Webhook receivers, dataset mirrors, and deployed model endpoints are the operator's runtime-layer responsibility (GDPR Article 19 requires telling each recipient the data was disclosed to about the erasure, unless that proves impossible or involves disproportionate effort, and telling the data subject about those recipients on request; Article 17(2) adds reasonable steps where the controller made the data public). The `data.erasure_warning_external_copies` event is the explicit reminder. +- **Make the whole response.** A complete response also needs steps ForgeLM does not perform: verifying the requester's identity with reasonable measures (recital 64; Article 12(6) lets you ask for more information where you have reasonable doubts); answering within one month of receipt (Article 12(3); extendable by two further months for complex or numerous requests, with notice in the first month); for an access request, giving the data subject a copy of the data and the Article 15(1)(a)-(h) information (Article 15(3)) and, where the data went to a third country, the Article 46 safeguards (Article 15(2)), without adversely affecting other people's rights (Article 15(4)); for an erasure request, deciding whether an Article 17(1) ground applies and which Article 17(3) exception, if any, covers what you keep; telling recipients (Article 19); re-running the access search after a purge to confirm the rows are gone; and, if you refuse a request, informing the requester without delay and at the latest within one month of the reasons, the possibility of complaining and the possibility of seeking a judicial remedy (Article 12(4)). Whether the trained model counts as personal data is a separate question (EDPB Opinion 28/2024). ## Article 15 — `forgelm reverse-pii` @@ -75,7 +78,7 @@ One event per invocation (see catalog): - `data.access_request_query` — emitted after the scan (or after a mid-scan I/O failure, with `error_*` fields). Carries the **hashed** `query_hash`, the `identifier_type`, the `scan_mode`, the list of `files_scanned`, the `match_count`, and the `salt_source`. -The chain row never carries the cleartext identifier — the salt protects against wordlist attacks against the audit log itself. +The chain row never carries the cleartext identifier, only a salted digest. The digest is personal data, not anonymous data (recital 26), and it counts as pseudonymisation (Article 4(5)) only if the additional information is kept separately under its own controls. By default the salt file sits next to the log, so whoever can read both can test a guessed identifier against it: set `FORGELM_AUDIT_SECRET` (it is XOR-ed into the salt) and protect the log and the salt like personal data. ### `--type custom` regex caveat @@ -87,21 +90,21 @@ The per-output-dir salt at `/.forgelm_audit_salt` (mode `0600`, atom The two primitives are intentionally separate: -- Rotating `FORGELM_AUDIT_SECRET` rotates **only the chain HMAC key**. Identifier hashes derive from the per-output-dir salt and are **stable across env-var rotations**, so a `forgelm reverse-pii` query mounted on the same `--output-dir` after a rotation still correlates with `forgelm purge` events recorded before it. +- Rotating `FORGELM_AUDIT_SECRET` rotates the chain HMAC key **and**, whenever the variable is set, the identifier-hash salt: `forgelm purge` XORs the first 16 bytes of the secret into the per-output-dir salt and records `salt_source=env_var`, so its `target_id` digests change with the secret. A plaintext `forgelm reverse-pii` query (no `--salt-source`) resolves the salt the same way, so it correlates with a purge run under the same secret state. `--salt-source` is a different feature: a hash-mask scan for corpora whose identifiers were hashed outside ForgeLM with this output directory's salt, which reports zero matches on a plaintext corpus even when the person is there. - Inspecting either primitive does not reveal the other. -- The per-output-dir salt is the only input to identifier hashing; it persists for the lifetime of the output dir, isolating the chain HMAC's rotation cadence from the cross-tool digest correlation. +- The per-output-dir salt is always an input to identifier hashing and persists for the lifetime of the output dir; the secret, when set, is a second input. ## Exit codes -Both subcommands follow the project's `0/1/2/3/4` contract: +Both subcommands use the first three codes of the project's public exit-code contract (0-6): | Code | Meaning | |---|---| -| 0 | Success. `forgelm purge --check-policy` exits 0 even when violations are found (report-not-gate semantic). `forgelm reverse-pii` exits 0 even when no matches are found (Article 15 explicitly accepts "no records" as a valid answer). | -| 1 | Config error: bad flags, unreadable corpus, malformed regex, conflicting retention values, unwritable `--audit-dir`. | -| 2 | Runtime error: I/O failure, permission denied, ReDoS SIGALRM timeout, atomic-rename failure. | +| 0 | Success. `forgelm purge --check-policy` exits 0 even when violations are found (report-not-gate semantic). `forgelm reverse-pii` exits 0 even when no matches are found (a zero-match scan only says the literal text is absent from the files you scanned; it does not by itself confirm that you hold no data about the person). | +| 1 | Config error: a missing `--kind`, `--row-id` without `--corpus`, malformed regex, no staging directory / compliance file matching `--run-id`, a `--config` that cannot be loaded. | +| 2 | Runtime error: I/O failure, permission denied, unwritable `--audit-dir`, an argument-parser usage error (two modes together, `--kind logs`), ReDoS SIGALRM timeout, atomic-rename failure. | -Codes 3 (`EXIT_EVAL_FAILURE`) and 4 (`EXIT_AWAITING_APPROVAL`) are not part of either subcommand's surface. +Codes 3 to 6 (`EXIT_EVAL_FAILURE`, `EXIT_AWAITING_APPROVAL`, `EXIT_WIZARD_CANCELLED`, `EXIT_INTEGRITY_FAILURE`) are not part of either subcommand's surface. ## Common pitfalls @@ -118,7 +121,7 @@ Codes 3 (`EXIT_EVAL_FAILURE`) and 4 (`EXIT_AWAITING_APPROVAL`) are not part of e ::: :::tip -**Wire `forgelm verify-audit` into your DSAR closeout.** Once the erasure / access event is in the chain, run `forgelm verify-audit /audit_log.jsonl --require-hmac` and attach the verification output to the DSAR ticket. The auditor will read it. +**Wire `forgelm verify-audit` into your DSAR closeout.** Once the erasure / access event is in the chain, run `forgelm verify-audit /audit_log.jsonl` and attach the verification output to the DSAR ticket. Add `--require-hmac` only if `FORGELM_AUDIT_SECRET` was set when the chain was written and is set now: the flag exits 1 without the secret and exits 6 if any line lacks an HMAC tag, so on a chain written without the secret it fails by design. The auditor will read it. ::: ## See also diff --git a/docs/usermanuals/en/compliance/gdpr.md b/docs/usermanuals/en/compliance/gdpr.md index d0f37a87..2ff22d6e 100644 --- a/docs/usermanuals/en/compliance/gdpr.md +++ b/docs/usermanuals/en/compliance/gdpr.md @@ -19,7 +19,7 @@ GDPR (Europe) and KVKK (Türkiye) impose data protection requirements on persona | **Integrity & confidentiality** | GDPR Art. 5(1)(f) | Protect against unauthorised access. | | **Accountability** | GDPR Art. 5(2) | You must be able to demonstrate compliance. | -KVKK (Türkiye) mirrors these principles closely. +KVKK (Türkiye, Law 6698) sets similar principles in its Article 4. Article 11 gives data subjects the right to learn whether their data are processed and to request erasure or destruction (Article 11(1)(e), under the conditions of Article 7, which also obliges the controller to erase, destroy or anonymise data once the reason for processing ends), and the controller must conclude a request within thirty days at the latest (Article 13(2)). KVKK has no separate accountability principle, and its security duties sit in Article 12. ## How ForgeLM addresses each @@ -31,7 +31,7 @@ PII masking at ingest replaces the identifier formats ForgeLM detects with a pla $ forgelm ingest ./policies/ --recursive --strategy markdown --pii-mask --output data/policies.jsonl ``` -The detectors cover email, phone, credit card, IBAN, and the Turkish, German, French and US national-ID formats; every match becomes `[REDACTED]`. **Names, addresses, dates of birth, health data and other free text are not detected**, and the patterns are tuned to miss a borderline match rather than redact ordinary prose. Masking reduces the personal data in the JSONL; it does not remove identifiable subjects from it. See [PII Masking](#/data/pii-masking). +The detectors cover email, phone, credit card, IBAN, and the Turkish, German, French and US national-ID formats; every match becomes `[REDACTED]`. **Names, addresses, dates of birth, health data and other free text are not detected**, and the patterns are tuned to miss a borderline match rather than redact ordinary prose. Masking reduces the personal data in the JSONL; it does not remove identifiable subjects from it. To measure how many names and locations (and, for some language models, organisations) remain, run `forgelm audit --pii-ml`: it adds Presidio NER **detection**, which reports per-category counts for each split (not which rows or which names) and masks nothing. It needs the `ingestion-pii-ml` extra and a spaCy model (for a language without a dedicated model, such as Turkish, `--pii-ml-language xx` uses `xx_ent_wiki_sm`). To find a known name, search for it with `forgelm reverse-pii`. See [PII Masking](#/data/pii-masking) and [ML-NER PII](#/data/pii-ml). ### Accountability (Art. 5(2)) @@ -53,7 +53,7 @@ retention: ephemeral_artefact_retention_days: 365 ``` -`forgelm purge --check-policy --config config.yaml` lists artefacts that are past their horizon and always exits 0. Nothing is deleted automatically and nothing is written to the audit log for it; deleting what is overdue is your storage layer's job. (`retention.enforce` is reserved: no command acts on it yet.) +`forgelm purge --check-policy --config config.yaml --output-dir ` lists the artefacts in that directory that are past their horizon and exits 0 however many it finds (without `--output-dir` it scans the current directory, not `training.output_dir`). Nothing is deleted automatically and nothing is written to the audit log for it; deleting what is overdue is your storage layer's job. (`retention.enforce` is reserved: no command acts on it yet.) ForgeLM does not create `raw_documents/` or `ingestion_output/`, so `raw_documents_retention_days` has an effect only if you keep source documents under `/raw_documents/` or `ingestion_output/`. ## Data subject requests @@ -66,17 +66,18 @@ Most-common request types and how ForgeLM helps: Search your training data for the person's identifier: ```shell -$ forgelm reverse-pii --query "ali@example.com" data/*.jsonl +$ forgelm reverse-pii --query "ali@example.com" --output-dir ./outputs data/*.jsonl +No matches for identifier hash 3f2a9c1e5b7d4a60… (literal, plaintext mode) in 3 file(s). ``` -The scan reports the lines where the identifier appears; zero matches is a valid answer. It finds what is literally in the files you point it at. Masking only removes the identifier formats it detects, so a name or an undetected variant can remain, and the raw source documents are not covered by a scan of the masked JSONL: search those separately. ForgeLM cannot tell you what a trained model remembers. +The scan reports the file and line where the identifier appears (the snippets are raw corpus text, so do not redirect them to a persistent log); zero matches, as above, means only that the literal text is in none of the files you scanned. It does not by itself show that you hold no data about the person, and an Article 15 response needs more than a search result: a copy of the data and the Article 15(1)(a)-(h) information (see [GDPR Erasure & Access](#/compliance/gdpr-erasure)). The printed value is the first 16 hex digits of the salted identifier hash that the audit event records, not the identifier. `--output-dir` is where ForgeLM keeps the per-directory salt (`.forgelm_audit_salt`) and appends the `data.access_request_query` event: give it the same directory you give `forgelm purge` so both events join one chain. Without it, both are written next to the first corpus file and ForgeLM logs a warning; with a `data/*.jsonl` glob that puts `audit_log.jsonl` among your corpus files, so always pass `--output-dir`. It finds what is literally in the files you point it at: the match is case-sensitive and compares raw file text, so search spelling and case variants as well (`--type custom --query '(?i)…'`), and note that a corpus written with ASCII escapes such as `\u00e7` hides non-ASCII names from a literal query. Masking only removes the identifier formats it detects, so a name or an undetected variant can remain, and the raw source documents are not covered by a scan of the masked JSONL: search those separately. ForgeLM cannot tell you what a trained model remembers. ### Right to erasure (Art. 17) "Delete my data." If a person's data was in your training set: -1. Find the rows with `forgelm reverse-pii`, then remove them with `forgelm purge --row-id --corpus `; every erasure is written to the audit log. `--row-id` is matched against the row's `id` or `row_id` field and never against a line number, and `forgelm ingest` does not write either field, so give every row a stable id **before you train** (see below). A corpus without ids cannot be purged row by row. +1. Find the rows with `forgelm reverse-pii`, then remove them with `forgelm purge --row-id --corpus --output-dir ./outputs` (the same directory as `reverse-pii`; use your training output directory so purge can also warn about a trained `final_model/`); every erasure is written to the audit log. `--row-id` is matched against the row's `id` or `row_id` field and never against a line number, and `forgelm ingest` does not write either field, so give every row a stable id **before you train** (see below). A corpus without ids cannot be purged row by row. 2. Source documents may still hold it — drop them from your raw store and re-ingest if necessary. 3. The model may have memorised some details — see "model-level erasure" below. @@ -106,11 +107,11 @@ finally: os.remove(tmp) ``` -Train from the file with ids. `forgelm reverse-pii` reports the file and line where the identifier occurs; read that row's `id` from the file and pass it to `forgelm purge --row-id`. Re-assigning ids after training breaks the link between the audit log and the corpus, so assign once. +Train from the file with ids. `forgelm reverse-pii` reports the file and line where the identifier occurs; read that row's `id` from the file and pass it to `forgelm purge --row-id`. Re-assigning ids after training breaks the link between the audit log and the corpus, so assign once. Once you have checked the new file, move or delete the original `data/train.jsonl`: it still holds every row without an id, so a `data/*.jsonl` glob matches both copies, `reverse-pii` reports the person in each, and `purge` edits only the file you name with `--corpus`. ### Model-level erasure -LLMs can memorise rare strings from training. Even with PII masking, removing all traces from a deployed model is hard. ForgeLM's defences: +LLMs can memorise rare strings from training. Even with PII masking, removing all traces from a deployed model is hard. EDPB Opinion 28/2024 says a model trained on personal data is not always anonymous, so whether yours is has to be assessed case by case; until you can show that it is, plan your answers to access and erasure requests as if the trained weights might still contain personal data. ForgeLM's defences: - **Reduce what goes in:** PII masking and deduplication (memorised data is usually duplicated data). - **Memorisation is not measured:** ForgeLM does not test whether a trained model reproduces a given string. diff --git a/docs/usermanuals/en/compliance/human-oversight.md b/docs/usermanuals/en/compliance/human-oversight.md index 48fa2f2a..3d17124b 100644 --- a/docs/usermanuals/en/compliance/human-oversight.md +++ b/docs/usermanuals/en/compliance/human-oversight.md @@ -95,7 +95,7 @@ ForgeLM does **not** ship a built-in quorum gate. There is no `approval.quorum` ## Timeouts -ForgeLM does **not** time out staged runs. There is no `approval.timeout_hours`, no `human_approval.timeout` event, and no auto-fail clock. A staged run waits indefinitely; if the deployer wants to expire stale staging directories, configure `retention.staging_ttl_days` instead — that wires into the GDPR Article 17 retention pipeline (which physically prunes the directory after the configured horizon, not into the approval gate). +ForgeLM does **not** time out staged runs. There is no `approval.timeout_hours`, no `human_approval.timeout` event, and no auto-fail clock. A staged run waits indefinitely. `retention.staging_ttl_days` does not change that: it only makes `forgelm purge --check-policy` list staging directories older than the horizon. Nothing is deleted or expired automatically, and the report never touches the approval gate. Removing a staging directory yourself (`forgelm purge --run-id --kind staging`) discards the pending model and leaves the approval undecided, so record a `forgelm reject` first if you want the decision on the audit log. ## Inspecting pending runs @@ -141,7 +141,7 @@ A `--show` against a granted / rejected run prints the full timeline (request ::: :::warn -**No quorum for shipping decisions.** For high-risk production deployments, single-reviewer approval is insufficient. Always require quorum >= 2. +**No quorum for shipping decisions.** ForgeLM records one approver per decision. If your risk assessment calls for more than one reviewer, enforce it outside ForgeLM as described above (Article 14(5) of the EU AI Act asks for an identification to be separately verified by at least two natural persons only for the remote biometric identification systems in Annex III point 1(a), with an exception for law enforcement, migration, border control and asylum where Union or national law finds it disproportionate). ::: :::tip diff --git a/docs/usermanuals/en/compliance/model-card.md b/docs/usermanuals/en/compliance/model-card.md index d43adf35..1f9471c0 100644 --- a/docs/usermanuals/en/compliance/model-card.md +++ b/docs/usermanuals/en/compliance/model-card.md @@ -88,7 +88,7 @@ model: name_or_path: meta-llama/Llama-3.1-8B-Instruct max_length: 2048 load_in_4bit: true - ... + # ... ``` ## Usage diff --git a/docs/usermanuals/en/data/audit.md b/docs/usermanuals/en/data/audit.md index 8c720938..972c27b6 100644 --- a/docs/usermanuals/en/data/audit.md +++ b/docs/usermanuals/en/data/audit.md @@ -137,7 +137,7 @@ Authoritative source: `forgelm/cli/_parser.py::_add_audit_subcommand`. | `--quality-filter` / `--no-quality-filter` | Heuristic quality checks (mean word length, alphabetic-character ratio, end-of-line punctuation ratio, repeated-line ratio, short-paragraph ratio). **Default ON from v0.6.0** (Phase 15 Task 5); pre-v0.6.0 the flag was opt-in. Pass `--no-quality-filter` to skip. Populates `quality_summary` in the report. See [Quality Filter](#/data/quality-filter). | | `--croissant` | Emit a [Google Croissant 1.0](https://mlcommons.org/croissant/) dataset card under the report's `croissant` key. See [Croissant 1.0 Dataset Card](#/data/croissant-card). | | `--pii-ml` | Layer Presidio's ML-NER PII detection on top of the regex detector. Requires the optional `forgelm[ingestion-pii-ml]` extra **and** a spaCy NER model (e.g. `python -m spacy download en_core_web_lg`). See [ML-NER PII (Presidio)](#/data/pii-ml). | -| `--pii-ml-language LANG` | spaCy NLP language code for `--pii-ml` (default `en`). Set to e.g. `tr` on a Turkish corpus AND make sure the matching spaCy model is installed. | +| `--pii-ml-language LANG` | spaCy NLP language code for `--pii-ml` (default `en`). Set to e.g. `de` for German, or `xx` (the multilingual `xx_ent_wiki_sm` fallback) for a language without a dedicated spaCy model such as Turkish; the matching spaCy model must be installed. | | `--workers N` | Worker processes for the split-level pipeline (default 1, sequential). Set to 2-4 on multi-split corpora for near-linear speedup. The audit JSON is byte-identical across worker counts (determinism contract). | | `--output-format {text,json}` | Stdout renderer. The `json` mode prints a machine-readable summary; `text` is the default human-readable form. | | `--allow-secrets` | Record credential findings without failing the run (exit `0` with a `SUPPRESSED` warning instead of `3`). The scan itself cannot be disabled. | diff --git a/docs/usermanuals/en/data/ingestion.md b/docs/usermanuals/en/data/ingestion.md index babb2d20..1fe04e3a 100644 --- a/docs/usermanuals/en/data/ingestion.md +++ b/docs/usermanuals/en/data/ingestion.md @@ -126,7 +126,7 @@ that appear most often: ::: :::tip -**Always audit after ingest.** A clean ingest doesn't mean a clean dataset. Run `forgelm audit data/output.jsonl` to check for cross-split leakage, near-duplicates, and PII the masking missed. +**Always audit after ingest.** A clean ingest doesn't mean a clean dataset. Run `forgelm audit data/output.jsonl` to check for cross-split leakage, near-duplicates, and PII the masking did not cover (`forgelm audit` re-applies the same regexes, so add `--pii-ml` to count names and locations). ::: ## See also diff --git a/docs/usermanuals/en/data/pii-masking.md b/docs/usermanuals/en/data/pii-masking.md index 580ccd7a..776dbc30 100644 --- a/docs/usermanuals/en/data/pii-masking.md +++ b/docs/usermanuals/en/data/pii-masking.md @@ -5,7 +5,7 @@ description: Detect and redact emails, phones, credit cards, IBAN, and national # PII Masking -Personal data in your training set is a regulatory hazard (GDPR Article 5(1)(c) — data minimisation) and an operational hazard (the model memorises and emits it). ForgeLM's PII masker detects nine categories of PII and redacts them at ingest time, before chunks land in JSONL. +Personal data in your training set is a regulatory hazard (GDPR Article 5(1)(c) — data minimisation) and an operational hazard (the model memorises and emits it). ForgeLM's PII masker detects eight categories of PII and redacts them at ingest time, before chunks land in JSONL. ## What gets detected @@ -15,9 +15,9 @@ Personal data in your training set is a regulatory hazard (GDPR Article 5(1)(c) | **Phone** | `+90 532 123 45 67`, `(555) 123-4567` | E.164-compatible patterns + locale variants | | **Credit card** | `4111-1111-1111-1111` | Issuer prefix (Visa/MC/Amex/Discover/JCB/UnionPay) + Luhn check — not Luhn alone, which every IMEI passes | | **IBAN** | `TR12 0006 4000 0011 2345 6789 01` | Country-aware checksum | -| **National ID — Turkey** | 11-digit TC kimlik | Modulo-10 + modulo-11 checksums | -| **National ID — Germany** | Steuer-ID | Format + checksum | -| **National ID — France** | NIR (social security) | Format + key validation | +| **National ID — Turkey** | 11-digit TC kimlik | Two modulo-10 check digits (10th and 11th) | +| **National ID — Germany** | ID-card serial (Personalausweis), e.g. `T22000129` | Shape only: a letter, 7-8 digits and an optional check character; no checksum | +| **National ID — France** | NIR (social security number) | Shape only: sex, year, month, department, commune and order digits plus an optional two-digit key; the key is not verified | | **US SSN** | `123-45-6789` | Format + reserved-block exclusions | These eight are the complete set (`_PII_PATTERNS` in `forgelm/data_audit/_pii_regex.py`). **IP addresses are not detected** — earlier versions of this page listed an "IPv4 / IPv6 (off by default; opt in)" row, but no such pattern and no such opt-in exists. If your GDPR assessment treats IP addresses as personal data, you need a separate control for them. @@ -46,7 +46,7 @@ The placeholder is consistent across the dataset, so a model can still learn tha ## The placeholder emitted :::warn -**There is one placeholder, not one per category.** Every detected span — email, phone, credit card, IBAN, TC kimlik, Steuer-ID, NIR, US SSN — is replaced with the single literal `[REDACTED]`. Earlier versions of this page published a nine-row table of per-category tags (`[EMAIL_REDACTED]`, `[PHONE_REDACTED]`, `[IP_REDACTED]`, …). None of those strings exist anywhere in the codebase. A downstream consumer that parses redaction tags to recover *which* category was masked cannot do so — that information survives only in the audit report's `pii_summary` counts. +**There is one placeholder, not one per category.** Every detected span — email, phone, credit card, IBAN, TC kimlik, German ID-card serial, NIR, US SSN — is replaced with the single literal `[REDACTED]`. Earlier versions of this page published a nine-row table of per-category tags (`[EMAIL_REDACTED]`, `[PHONE_REDACTED]`, `[IP_REDACTED]`, …). None of those strings exist anywhere in the codebase. A downstream consumer that parses redaction tags to recover *which* category was masked cannot do so — that information survives only in the audit report's `pii_summary` counts. ::: The default is `[REDACTED]` (`mask_pii`'s `replacement` parameter). The parallel secrets masker uses `[REDACTED-SECRET]` — see [Secrets Scrubbing](#/data/secrets). Note in the example above that the phone pattern does not always consume the full number; verify masking against your own formats before relying on it. @@ -55,8 +55,8 @@ The default is `[REDACTED]` (`mask_pii`'s `replacement` parameter). The parallel The PII regexes are deliberately tuned for **low false-positive rate**. They prefer to miss a borderline match (false negative) than to redact a non-PII string in your prose (false positive). Reasons: -1. False positives silently corrupt your data — replacing legitimate words with `[EMAIL_REDACTED]` ruins examples. -2. The audit step catches what masking missed; you can decide per-row whether to fix or drop. +1. False positives silently corrupt your data — replacing legitimate words with `[REDACTED]` ruins examples. +2. Running `forgelm audit` over the masked output re-applies the same regexes, so it confirms those patterns no longer match; it cannot find identifiers the patterns do not describe (names, for example — `--pii-ml` counts those). 3. Aggressive regexes have caused real-world ML pipeline outages (the Phase 11.5 incident is documented in the contributor [regex standard on GitHub](https://github.com/HodeTech/ForgeLM/blob/main/docs/standards/regex.md)). If you need stricter detection — for instance, a high-stakes legal corpus — pair the masker with a manual review step. Don't push the regexes harder. @@ -67,20 +67,31 @@ To detect without modifying: ```shell $ forgelm audit data/policies.jsonl -⚠ PII: 18 emails, 4 phone, 2 IBAN (medium severity) +Data audit summary + Source : data/policies.jsonl + Total samples : 12240 + Splits : train + └─ train: n=12240 + length min=24 max=1180 mean=212.4 p95=640 + languages (top-3): en=12240 + PII : email=18, phone=4 + PII severity : worst tier = medium + by tier : medium=18, low=4 ``` -The audit report lists row indices and offsets, so you can inspect specific cases. +The audit report carries counts per category and split, not row indices or offsets. To find the rows that hold a specific identifier, search for it with `forgelm reverse-pii`. -## Locales +## Regional formats -| Locale | Phone | National ID | Notes | -|---|---|---|---| -| TR (default) | E.164 + Turkish formats | TC kimlik | Most heavily tuned. | -| DE | E.164 + German formats | Steuer-ID | | -| FR | E.164 + French formats | NIR | | -| US | E.164 + (xxx) xxx-xxxx | SSN with reserved-block exclusion | | -| Global | E.164 only | none | Fallback for unknown locales. | +| Format | What it matches | Checksum-validated | +|---|---|---| +| Phone (all countries) | One pattern: a `+` country prefix, or a parenthesised area code | No | +| TR national ID | 11 digits (TC kimlik) | Yes: two modulo-10 check digits (10th and 11th) | +| DE national ID | ID-card serial: a letter, 7-8 digits, an optional check character | No | +| FR national ID | NIR: 13 characters (digits, or `2A` / `2B` for Corsica) with an optional two-digit key | No | +| US SSN | `NNN-NN-NNNN`, reserved blocks excluded | No | + +Only credit cards, IBANs and TC kimlik numbers are checksum-validated; the other families are matched by shape only, so treat a finding as a candidate to review rather than a confirmed identifier. The regex-based PII layer triggered by `forgelm ingest --pii-mask` (or the audit equivalent) detects all patterns in the table above diff --git a/docs/usermanuals/en/data/pii-ml.md b/docs/usermanuals/en/data/pii-ml.md index db09c611..d43d5bb0 100644 --- a/docs/usermanuals/en/data/pii-ml.md +++ b/docs/usermanuals/en/data/pii-ml.md @@ -5,9 +5,9 @@ description: Optional Microsoft Presidio adapter that adds person / organization # ML-NER PII Detection — `--pii-ml` -The default [PII Masking](#/data/pii-masking) layer is regex-anchored and covers the *structured* identifiers GDPR Article 10 cares about (email, phone, IBAN, credit card, national IDs). Presidio NER adds the *unstructured* identifiers regex inherently misses: person names, organization names, geographic locations. +The default [PII Masking](#/data/pii-masking) layer is regex-anchored and covers the common *structured* identifiers (email, phone, IBAN, credit card, national IDs). They are personal data under GDPR Article 4(1) whenever they relate to an identified or identifiable natural person; Article 87 additionally lets Member States set the specific conditions for processing national identification numbers. Presidio NER adds the *unstructured* identifiers regex inherently misses: person names, organization names, geographic locations. -This is an **opt-in** layer. The default audit + ingest behaviour is unchanged when `--pii-ml` is not passed. +This is an **opt-in** layer. The default audit behaviour is unchanged when `--pii-ml` is not passed (`forgelm ingest` has no `--pii-ml` flag). ## When to use it @@ -38,10 +38,16 @@ Without the spaCy model, `forgelm audit --pii-ml` raises a typed `ImportError` * ```shell $ forgelm audit data/customer-letters.jsonl --output ./audit/ --pii-ml -✓ format: instructions (8,400 rows) -⚠ PII: 12 emails, 3 phone, 1 IBAN (regex layer) -⚠ PII (ML): 47 person, 18 organization, 9 location (Presidio) - worst tier: medium +Data audit summary + Source : data/customer-letters.jsonl + Total samples : 8400 + Splits : train + └─ train: n=8400 + length min=31 max=2210 mean=402.7 p95=1180 + languages (top-3): en=8400 + PII : email=12, location=9, person=47, phone=3 + PII severity : worst tier = medium + by tier : medium=59, low=12 ``` The Presidio findings merge into the same `pii_summary` and `pii_severity` blocks under disjoint category names, so the regex baseline stays visible alongside the ML signal: @@ -52,16 +58,17 @@ The Presidio findings merge into the same `pii_summary` and `pii_severity` block "email": 12, "phone": 3, "person": 47, - "organization": 18, "location": 9 }, "pii_severity": { - "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 30}, + "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 12}, "worst_tier": "medium" } } ``` +Presidio's default English configuration ignores the `organization` label (it produces many false positives), so with the default `--pii-ml-language en` the `organization` count stays 0 and the example above shows none; a language model that reports it, for example `--pii-ml-language xx`, adds an `organization` count. + ## Severity tiers The new categories sit under a dedicated table in `forgelm.data_audit.PII_ML_SEVERITY`: @@ -115,7 +122,7 @@ _require_presidio() text = "Alice Williams visits Acme Corp's Berlin office on Monday." counts = detect_pii_ml(text, language="en") print(counts) -# {'person': 1, 'organization': 1, 'location': 1} +# {'person': 1, 'location': 1} (Presidio's default English configuration ignores organizations) ``` The function returns an empty dict for non-string input, missing extras, or transient analyzer errors (a single bad row never blocks the audit). Hard failures (missing spaCy model, language not registered) are caught up front by `_require_presidio(language=...)` and raised before any rows are scanned — call it explicitly when you bypass `audit_dataset` and want the same pre-flight guarantees. diff --git a/docs/usermanuals/en/deployment/chat.md b/docs/usermanuals/en/deployment/chat.md index c02716a7..2c8fc71d 100644 --- a/docs/usermanuals/en/deployment/chat.md +++ b/docs/usermanuals/en/deployment/chat.md @@ -11,40 +11,52 @@ description: Sanity-check your fine-tuned model in a streaming REPL. ```shell $ forgelm chat ./checkpoints/customer-support -ForgeLM 0.7.0 — chat with checkpoints/customer-support -forgelm> how do I cancel my subscription? -You can cancel from Settings → Billing → Cancel subscription. Your access -continues until the end of the current billing period… +ForgeLM Chat — type your message and press Enter +Streaming : on | Temperature : 0.7 +Commands : /help /reset /save /temperature /system /exit +──────────────────────────────────────────────────────── -forgelm> /system You are a polite customer-support agent for a Turkish telecom. -[system prompt updated] +> how do I cancel my subscription? +Assistant: You can cancel from Settings → Billing → Cancel subscription. Your +access continues until the end of the current billing period… -forgelm> aboneliği nasıl iptal ederim? -Aboneliğinizi iptal etmek için Ayarlar → Faturalandırma → Aboneliği İptal Et -yolunu izleyebilirsiniz... +> /system You are a polite customer-support agent for a Turkish telecom. +[System prompt updated] + +> aboneliği nasıl iptal ederim? +Assistant: Aboneliğinizi iptal etmek için Ayarlar → Faturalandırma → Aboneliği +İptal Et yolunu izleyebilirsiniz... ``` +With `rich` installed, the banner is drawn inside a panel. A new system prompt applies to the turns that follow; it does not rewrite the conversation so far. + ## Slash commands | Command | What it does | |---|---| | `/reset` | Clear conversation history. | -| `/save ` | Save the conversation to JSONL. | -| `/system ` | Set or update the system prompt. | -| `/temperature ` | Set sampling temperature (0.0 to 2.0). | -| `/help` | Show this list. | -| `/exit` or `Ctrl+D` | Exit. | +| `/save [path]` | Save the conversation to JSONL; without a path it writes `chat_history_.jsonl` in the current directory. | +| `/system [prompt]` | Set or update the system prompt; without an argument it shows the current one. | +| `/temperature ` | Set sampling temperature: above 0 and up to 2.0 (`0` is rejected). | +| `/help` or `/?` | Show this list. | +| `/exit`, `/quit` or `Ctrl+D` | Exit. | ## Configuration -```yaml -chat: - default_temperature: 0.7 - default_top_p: 0.9 - default_max_tokens: 1024 - default_system_prompt: "You are a helpful assistant." - history_file: "~/.forgelm/chat-history" # persisted across sessions -``` +`forgelm chat` reads no YAML config and has no `chat:` block: it is set with command-line flags and the slash commands above. + +| Setting | Flag | Default | +|---|---|---| +| Adapter to merge before chatting | `--adapter DIR` (not supported with `--backend unsloth`) | none | +| Initial system prompt | `--system PROMPT` (change it later with `/system`) | none | +| Sampling temperature | `--temperature` (change it later with `/temperature`) | `0.7` | +| Maximum tokens per reply | `--max-new-tokens` | `512` | +| Streaming output | `--no-stream` turns it off | on | +| Quantized loading (CUDA with bitsandbytes only; ignored otherwise) | `--load-in-4bit` (NF4) or `--load-in-8bit`; 4-bit wins if both are given, and with `--backend unsloth` it is also the default when neither is | off | +| Model backend | `--backend transformers` or `--backend unsloth` | `transformers` | +| Run code bundled with the model | `--trust-remote-code` | off | + +Top-p, top-k and the repetition penalty are fixed (0.9 / 50 / 1.1) and cannot be set. Chat always samples: there is no seed and no greedy mode. There is no history file that persists across sessions; write a conversation out with `/save`. The command-line flag accepts a temperature of `0.0`, but sampling rejects it, so every turn then fails with a `[Generation error: …]` line (with streaming on, the default, only after a 60-second wait); use a value above 0. ## Loading a checkpoint @@ -53,7 +65,8 @@ chat: - A directory with adapter weights (LoRA): `./checkpoints/run/` - A merged checkpoint directory: `./checkpoints/run/merged/` - A HuggingFace model ID: `Qwen/Qwen2.5-7B-Instruct` -- A GGUF file: `./model.gguf` (uses llama.cpp under the hood) + +GGUF files are not loaded by `forgelm chat`: the command loads Hugging Face checkpoints only. To try a GGUF export, run it in Ollama or llama.cpp (see [Deploy Targets](#/deployment/deploy-targets)). For LoRA checkpoints, pass the base model as the positional argument and the adapter directory via `--adapter`: @@ -68,7 +81,7 @@ $ forgelm chat "Qwen/Qwen2.5-7B" --adapter ./checkpoints/run/ Under the planned `--safety` flag (preview only — see the note above), every prompt and response would be screened by Llama Guard: ```text -forgelm> [adversarial prompt] +> [adversarial prompt] [Llama Guard flagged S2 (non-violent crimes) — refusing] I can't help with that. Try rephrasing the question. ``` @@ -77,28 +90,26 @@ Useful when probing the model for jailbreaks before deployment. Off by default f ## Multi-turn handling -Conversation history is preserved within a session. The model sees the full chat (or as much as fits in `max_length`): +Conversation history is preserved within a session. With every prompt the model receives the system prompt and the last 50 question-and-answer pairs; older pairs stay in memory (and in a `/save` file) but are no longer sent. The limit counts pairs, not tokens: ```text -forgelm> what's the capital of Türkiye? -Ankara. +> what's the capital of Türkiye? +Assistant: Ankara. -forgelm> what's its population? -[uses prior context: "its" refers to Ankara] -About 5.7 million as of recent estimates... +> what's its population? +Assistant: About 5.7 million as of recent estimates... ``` -To start fresh: `/reset`. +The second answer is only possible because the first turn is sent along with it ("its" refers to Ankara). To start fresh: `/reset`. ## Saving and replaying ```shell -forgelm> /save sessions/qa-1.jsonl -[saved 6 turns to sessions/qa-1.jsonl] +> /save sessions/qa-1.jsonl +[History saved → sessions/qa-1.jsonl (7 messages)] ``` -The saved JSONL is replayable by feeding it back through a fresh -session's prompt history (replay tooling is roadmapped for a future Pro CLI release). +The file holds one `{"role": ..., "content": ...}` message per line: the system prompt first (when one is set), then every user and assistant message of the session. The system prompt written is the one in force when you save, even if you changed it mid-conversation. There is no command to load a saved file back into a session (replay tooling is roadmapped for a future Pro CLI release). Sessions are useful for: - Reproducing a bug you found during testing. @@ -122,11 +133,11 @@ Win rate v2 vs v1: 0.62 (sig p=0.04) ## Common pitfalls :::warn -**Treating `/temperature 0` as deterministic.** It's near-deterministic, but tiebreaks in argmax sampling can still produce minor variance. For exact reproducibility, set `seed:` in YAML. +**Treating chat as reproducible.** Chat always samples, and it has no seed and no greedy mode (`/temperature 0` is rejected), so the same prompt can give different answers. Use it to explore; keep a `/save` transcript of what the model actually said, and use the evaluation commands for repeatable checks. ::: :::warn -**Long conversations exceeding context.** Once history exceeds `max_length`, ForgeLM drops oldest messages first. The conversation can lose continuity unexpectedly. Use `/reset` periodically for long testing sessions. +**Long conversations exceeding context.** Only the last 50 pairs are sent, but nothing counts tokens: a long system prompt, long answers or a small-context model can still overflow. Depending on the model, the turn then fails with a `[Generation error: …]` line (it is not added to the history) or generation continues with degraded output and only a warning from `transformers`. Use `/reset` periodically for long testing sessions. ::: :::tip diff --git a/docs/usermanuals/en/evaluation/safety.md b/docs/usermanuals/en/evaluation/safety.md index d0cbc39c..95d34ddb 100644 --- a/docs/usermanuals/en/evaluation/safety.md +++ b/docs/usermanuals/en/evaluation/safety.md @@ -187,7 +187,7 @@ The set is a *seed* — augment with your own per-domain probes before treating This example was produced under the default `classifier_mode: generation` (see the warning above): `safety_score` equals `safe_ratio` exactly because `confidence_weighted` degenerates to a safe-ratio average in that mode, and `details[].confidence` is `1.0` for a well-formed `unsafe` verdict — not a real probability. `failure_reason` comes from the always-active absolute gate in `forgelm/safety/_gates.py::_evaluate_safety_gates`: `unsafe_count=3` of `total_count=50` is a 6.00% unsafe ratio, which exceeds the default `max_safety_regression=0.05` (5.00%) ceiling — this gate fires regardless of `scoring_method`. `severity_distribution` always lists all four severity levels (`critical`/`high`/`medium`/`low`), zero-filled, when `track_categories: true`; here both unsafe, well-formed, category-tagged responses were `S5` (defamation), which `forgelm/safety/_types.py`'s `CATEGORY_SEVERITY` maps to `medium`, not `high`. The third unsafe response (counted in `low_confidence_count`) was a malformed guard verdict — scored fail-closed and excluded from the category/severity breakdown. That is what `unscored_count: 1` records, and why `scored_unsafe_count` is `2`: those two fields partition `unsafe_count` into pairs the classifier actually read as unsafe versus pairs it could not answer on at all. Read them before acting on a failure — a `failure_reason` quoting an unsafe ratio always appends the same decomposition in prose, because six malformed verdicts and six genuinely harmful completions otherwise produce an identical sentence. `evaluation_completed` is the field auto-revert keys on: `false` means the run is not usable evidence about the model (the classifier was unusable, or the failure was attributable entirely to unscored pairs), so the model is **failed but kept**, and `forgelm safety-eval` exits `2` rather than `3`. -`category_distribution` / `severity_distribution` are only present when `track_categories: true`. `details[].prompt`, `details[].response` and `details[].raw_verdict` are stripped by default for GDPR / EU AI Act Art. 10 privacy — set `include_eval_samples: true` to persist the raw text for debugging. Note what the third field is: under `classifier_mode: generation` (the default) `raw_verdict` is the guard's own generated output, truncated to 200 characters. It is the field to read when a run reports that the evaluation could not be performed — but a misconfigured guard echoes or continues the adversarial probe rather than answering it, so enabling this switch can write probe text to disk by a second route. `details[].label` stays in the artefact either way; it is rebuilt from a fixed vocabulary rather than sliced out of model output. That vocabulary is closed and space-separated: `safe`, `malformed`, or `unsafe` optionally followed by comma-joined S-codes — `"unsafe"`, `"unsafe S5"`, `"unsafe S1,S5"`. Parse on whitespace, not on a newline: earlier versions of this page showed `"unsafe\nS5"`, which was the pre-rewrite shape produced when the label was sliced out of raw model output. A consumer splitting on `\n` extracts no S-codes from any current artefact. +`category_distribution` / `severity_distribution` are only present when `track_categories: true`. `details[].prompt`, `details[].response` and `details[].raw_verdict` are stripped by default for GDPR Art. 25(2) data protection by default — set `include_eval_samples: true` to persist the raw text for debugging. Note what the third field is: under `classifier_mode: generation` (the default) `raw_verdict` is the guard's own generated output, truncated to 200 characters. It is the field to read when a run reports that the evaluation could not be performed — but a misconfigured guard echoes or continues the adversarial probe rather than answering it, so enabling this switch can write probe text to disk by a second route. `details[].label` stays in the artefact either way; it is rebuilt from a fixed vocabulary rather than sliced out of model output. That vocabulary is closed and space-separated: `safe`, `malformed`, or `unsafe` optionally followed by comma-joined S-codes — `"unsafe"`, `"unsafe S5"`, `"unsafe S1,S5"`. Parse on whitespace, not on a newline: earlier versions of this page showed `"unsafe\nS5"`, which was the pre-rewrite shape produced when the label was sliced out of raw model output. A consumer splitting on `\n` extracts no S-codes from any current artefact. `safety_trend.jsonl` appends one JSON object per run: @@ -211,7 +211,7 @@ This example was produced under the default `classifier_mode: generation` (see t | `track_categories` | bool | `false` | Parse Llama Guard S1-S14 categories per response and surface in the report. | | `severity_thresholds` | `Optional[Dict[str,float]]` | `null` | Per-severity unsafe-ratio ceilings — see Severity thresholds above. | | `batch_size` | int | `8` | Batched generation size for the fine-tuned model's probe responses; `1` disables batching. Does **not** apply to guard-verdict scoring, which is always sequential — see Common pitfalls below. | -| `include_eval_samples` | bool | `false` | Persist raw `prompt` / `response` / `raw_verdict` strings to `safety_results.json`. Off by default for GDPR / EU AI Act Art. 10 privacy — `raw_verdict` is the guard's own generated text under `classifier_mode: generation`. | +| `include_eval_samples` | bool | `false` | Persist raw `prompt` / `response` / `raw_verdict` strings to `safety_results.json`. Off by default for GDPR Art. 25(2) data protection by default — `raw_verdict` is the guard's own generated text under `classifier_mode: generation`. | ## Common pitfalls diff --git a/docs/usermanuals/en/operations/gpu-cost.md b/docs/usermanuals/en/operations/gpu-cost.md index 504b0466..1dbac863 100644 --- a/docs/usermanuals/en/operations/gpu-cost.md +++ b/docs/usermanuals/en/operations/gpu-cost.md @@ -1,155 +1,157 @@ --- title: GPU Cost Estimation -description: Auto-detect across 16 GPU profiles and track per-run cost against your hourly rate. +description: Detect the GPU, price the run from your hourly rate or a built-in table, and record the cost of every run. --- # GPU Cost Estimation -> **Status:** GPU detection + per-run duration + audit-log stamping ship -> today; the config-driven `cost_tracking:` block (rate tables, alert / +> **Status:** GPU detection, the training duration and a cost estimate are +> recorded today for every run that passes its evaluation-loss and benchmark gates, and `training.gpu_cost_per_hour` is the one cost +> setting. A config-driven `cost_tracking:` block (per-GPU rate tables, alert / > halt thresholds) is **tracked on the roadmap and not yet implemented** — -> `forgelm/config.py` has no `output.cost_tracking` surface. Examples below -> that show `cost_tracking:` fields are forward-looking placeholders — set -> hourly rates manually until the YAML surface lands. The deferral is -> tracked in the [risks-and-decisions roadmap on GitHub](https://github.com/HodeTech/ForgeLM/blob/main/docs/roadmap/risks-and-decisions.md). +> `forgelm/config.py` has no `output.cost_tracking` surface, so no example on +> this page uses one. The deferral is tracked in the +> [risks-and-decisions roadmap on GitHub](https://github.com/HodeTech/ForgeLM/blob/main/docs/roadmap/risks-and-decisions.md). -ForgeLM detects the GPU you're running on, looks up its profile (memory, compute, typical hourly rate), and tracks per-run cost. After every run, the audit log records exactly how much GPU time was used and what it cost. +When a training run passes its evaluation-loss and benchmark gates (a run that fails one returns before the cost is collected), ForgeLM records how long it trained, on how many GPUs, and what that cost at an hourly rate: yours if you set one, otherwise a built-in price for the detected GPU. The estimate is written next to the other run results; it is not a bill, and it does not stop or alert anything. ## How detection works -On startup, `forgelm` reads: -- `nvidia-smi --query-gpu=name,memory.total,...` for hardware identification. -- The matching hourly rate from the built-in GPU price table in `forgelm/trainer.py`. +After training, ForgeLM asks PyTorch for the first CUDA device's name (`torch.cuda.get_device_name(0)`), the number of CUDA devices the process can see, and the peak memory allocated on the first device. It takes the training time from the Hugging Face trainer's `train_runtime`. It does not call `nvidia-smi`. Without a CUDA GPU none of the GPU fields are recorded. -Supported GPUs include: +The rate comes from `training.gpu_cost_per_hour` when set, and otherwise from the price table `_GPU_PRICING` in `forgelm/trainer.py`, which is matched against the exact device name that PyTorch reports: -| Class | Models | +| Class | Recognised names | |---|---| -| Datacenter | A100 40 GB / 80 GB, H100 80 GB, H200, L40S | -| Workstation | RTX 6000 Ada, RTX A6000 | -| Consumer | RTX 4090, RTX 4080, RTX 3090, RTX 3080 | -| Cloud-only | T4, V100, A10G | -| Apple | M1/M2/M3 Max (CPU/MPS fallback) | +| Tesla | Tesla T4, Tesla P100, Tesla V100, Tesla K80 | +| NVIDIA data center | NVIDIA A10G, NVIDIA A100-SXM4-40GB, NVIDIA A100-SXM4-80GB, NVIDIA A100 80GB PCIe, NVIDIA H100 80GB HBM3, NVIDIA H100 SXM5 80GB, NVIDIA H200, NVIDIA L4, NVIDIA L40S, NVIDIA B200 | +| GeForce (self-hosted) | NVIDIA GeForce RTX 3090, NVIDIA GeForce RTX 4090 | -If your GPU isn't recognised, ForgeLM logs a warning and falls back to a generic profile (no cost estimation, but training works). +The prices in that table are approximate on-demand cloud averages (for the GeForce cards, an estimate of electricity plus amortisation). A GPU that is not in the table, for example `NVIDIA RTX A6000` or `NVIDIA H100 PCIe`, is **not** reported with a warning: the run simply has no estimated cost unless you set `training.gpu_cost_per_hour`. ## Configuring your hourly rate ```yaml -output: - cost_tracking: - enabled: true - rate_per_hour: - A100_80GB: 1.10 # USD per hour - A100_40GB: 0.85 - H100_80GB: 2.40 - RTX_4090: 0.50 # rough cost-of-electricity - default: 1.00 # for unmatched GPUs - currency: "USD" +training: + gpu_cost_per_hour: 1.10 # USD per hour, per GPU ``` -Once the `cost_tracking:` surface lands, set this once per project and ForgeLM will use the matched rate for cost reporting. Until then, the block is a forward-looking placeholder (see the status banner above). +Set `training.gpu_cost_per_hour` to your real rate. When it is unset, ForgeLM falls back to its built-in price table for the detected GPU; the run's `resource_usage` records which one it used in `cost_source`: + +| `cost_source` | Meaning | +|---|---| +| `user_config` | The rate came from `training.gpu_cost_per_hour` (`0` is accepted and records a zero cost). | +| `auto_detected` | The device name matched a table entry exactly. | +| `fuzzy_match` | The device name partly matched a table entry. This is a guess: `NVIDIA L40`, for instance, is priced as `NVIDIA L40S`. | + +When no rate resolves, `cost_source`, `gpu_cost_per_hour_usd` and `estimated_cost_usd` are absent. + +:::warn +**Machines without a CUDA GPU (CPU, Apple silicon).** There is no device name, and an empty name partly matches every table entry and the longest one, `NVIDIA GeForce RTX 3090`, wins, so the run is recorded at a placeholder `0.15` USD per hour with `cost_source: fuzzy_match`. Do not read that as a real cost; set `training.gpu_cost_per_hour` explicitly on such machines (`0` if the run is free to you). +::: + +There is one rate per run, and it is per GPU: per-GPU-model rate tables are not supported. ## Output -After each run, the audit log records: +The estimate appears in these places: + +- **`compliance/compliance_report.json`**, under `resource_usage`, in the run's `training.output_dir`; it is written at the end of a run that also passes the safety and judge gates (a reverted run exports no report). The same data is copied into `annex_iv_metadata.json` as `computational_resources`. +- **The `--output-format json` result**, as `resource_usage` and a top-level `estimated_cost_usd`. +- **The console**, as an `Estimated training cost` line and a `GPU-hours` line that names the pricing source (text mode). +- **The run metrics**: every numeric field is copied in as `resource/` (for example `resource/estimated_cost_usd`). The copies appear in the result's `metrics`, in the success webhook and in the `metrics_summary` of the `pipeline.completed` audit event (not emitted when the run stops at the human-approval gate). They are not sent to W&B, MLflow or TensorBoard. + +A 4-GPU run that trained for 1892 seconds at `gpu_cost_per_hour: 1.10` records: ```json { - "event": "run_complete", - "ts": "2026-04-29T14:33:10Z", - "duration_seconds": 1892, - "gpu_profile": "A100_80GB", - "gpus_used": 1, - "estimated_cost_usd": 0.578, - "rate_per_hour": 1.10, - "currency": "USD" + "resource_usage": { + "gpu_model": "NVIDIA A100-SXM4-80GB", + "peak_vram_gb": 41.7, + "gpu_count": 4, + "training_duration_seconds": 1892.0, + "gpu_hours": 2.102, + "cost_source": "user_config", + "gpu_cost_per_hour_usd": 1.1, + "estimated_cost_usd": 2.3124 + } } ``` -The model card cites it: - -```markdown -## Training cost - -This model was trained for 31m 32s on 1× A100 80GB, -estimated at $0.58 USD at the configured rate. -``` +The model card does not include the cost. ## Pre-flight cost estimation -Before starting a long training run, run a short calibration (1-2 steps with `training.max_steps: 2`), capture the resulting `gpu_hours` from the per-run `compliance_report.json`, and multiply by your provider's hourly rate. (A dedicated `--estimate-cost` flag was discussed but not shipped; the resource-tracking path emits actuals only.) +Before starting a long training run, run a short calibration with a hard step cap, read `gpu_hours` from its `resource_usage`, scale it to the full step count and multiply by your hourly rate. A dedicated `--estimate-cost` flag was discussed but not shipped; the resource-tracking path records actuals only. + +```yaml +# configs/calibration.yaml — your real config, plus: +training: + max_steps: 20 + output_dir: /tmp/calib +``` ```shell -$ forgelm --config configs/calibration.yaml --output-dir /tmp/calib -$ jq '.resource_usage.gpu_hours' /tmp/calib/compliance_report.json +$ forgelm --config configs/calibration.yaml +$ jq '.resource_usage.gpu_hours' /tmp/calib/compliance/compliance_report.json 0.034 -$ python -c "print(0.034 * (3 / 0.034) * 1.10)" # 6h training at $1.10/hr -$7.15 +$ python -c "print(round(0.034 * (3000 / 20) * 1.10, 2))" # 3,000 steps at $1.10 per GPU-hour +5.61 ``` -The calibration approach is typically within 20% of actual. +Treat the result as a rough figure. The first steps include warm-up, and the mix of sequence lengths in a few steps rarely matches the whole dataset, so run enough steps to see a stable step time before scaling it. ## Multi-GPU and distributed -For multi-GPU training, ForgeLM multiplies the per-GPU rate by the GPU count: - -```yaml -output: - cost_tracking: - rate_per_hour: - A100_80GB: 1.10 -``` +`gpu_hours` is the training time multiplied by the number of CUDA devices visible to the process, and the single `training.gpu_cost_per_hour` rate is applied per GPU. A 4×A100 run for 2 hours = 4 × 2 × $1.10 = $8.80, regardless of whether you use ZeRO or FSDP. +If the job uses fewer GPUs than the machine has, restrict the devices it can see (for example with `CUDA_VISIBLE_DEVICES`), or the estimate counts GPUs the run never used. + ## Cost alerts (roadmap — not yet implemented) -For runs that may run away, the planned `cost_tracking` block will support -threshold-based alerts and halts: +For runs that may run away, the planned `cost_tracking` block would support +threshold-based alerts (a webhook fires when an alert threshold is crossed) and +halts (training stops at a halt threshold). No such setting exists in +`forgelm/config.py` today. -```yaml -# roadmap placeholder — not yet honoured by forgelm/config.py -output: - cost_tracking: - alert_threshold_usd: 50.0 # webhook fires when crossed - halt_threshold_usd: 200.0 # training stops -``` - -When implemented, the alert will fire the configured webhook (see [Webhooks](#/operations/webhooks)) — useful in CI to catch a misconfigured run before it spends a week of budget overnight. Until then, monitor cost manually via the audit log + a budget-side guardrail in your scheduler. +When implemented, the alert will fire the configured webhook (see [Webhooks](#/operations/webhooks)) — useful in CI to catch a misconfigured run before it spends a week of budget overnight. Until then, monitor cost manually via the recorded `resource_usage` and a budget-side guardrail in your scheduler. ## Custom GPU profiles -To add a GPU not in the default profile: +:::warn +**Not implemented.** ForgeLM has no `gpu_profiles` setting and reads no `gpu_profiles.local.yaml`; the snippet below is the planned shape only and a config that contains it is rejected. Today the way to price a GPU that is not in the table is `training.gpu_cost_per_hour`, which works for any GPU. +::: + +To add a GPU not in the built-in table (planned): ```yaml gpu_profiles: custom: - name: "AcmeNVIDIA-XYZ" - pattern: "AcmeNVIDIA-XYZ" # match against nvidia-smi name + pattern: "AcmeNVIDIA-XYZ" # match against the device name memory_gb: 96 compute_capability: 9.0 tensor_cores: true ``` -Drop this in your project root as `gpu_profiles.local.yaml`; ForgeLM auto-merges it. - ## Common pitfalls :::warn -**Hourly rate as gospel.** ForgeLM's defaults are reasonable averages — your actual cloud bill depends on instance type, region, spot vs on-demand, and discounts. Override with your real rate. +**Hourly rate as gospel.** ForgeLM's defaults are rough averages — your actual cloud bill depends on instance type, region, spot vs on-demand, and discounts. Override with your real rate. ::: :::warn -**Multi-tenancy on shared GPUs.** If multiple jobs share a GPU (rare for training, common for inference), the cost-tracking multiplies rather than divides. Use `--gpus N` to declare actual allocation. +**Shared GPUs.** The estimate bills the whole training time on every visible GPU; there is no setting for a fractional or shared allocation. If other jobs share the GPU, divide the figure yourself. ::: :::tip -**Track cost over time.** The audit log includes cost for every run; trend it over weeks to catch creeping overruns. A 2× cost increase for the same training time is usually a sign that data grew or hyperparameters drifted. +**Track cost over time.** Every run records its cost; trend it over weeks to catch creeping overruns. A 2× cost increase for the same training time is usually a sign that data grew or hyperparameters drifted. ::: ## See also - [VRAM Fit-Check](#/operations/vram-fit-check) — runs alongside cost estimation. - [CI/CD Pipelines](#/operations/cicd) — cost alerts in CI. -- [Configuration Reference](#/reference/configuration) — full `cost_tracking` block. +- [Configuration Reference](#/reference/configuration) — the `training.gpu_cost_per_hour` field. diff --git a/docs/usermanuals/en/reference/cli.md b/docs/usermanuals/en/reference/cli.md index 5ee639a8..beb37503 100644 --- a/docs/usermanuals/en/reference/cli.md +++ b/docs/usermanuals/en/reference/cli.md @@ -26,8 +26,8 @@ ForgeLM ships a single `forgelm` binary with subcommands. This page is the canon | `forgelm approve` | Sign a human approval request and promote `final_model.staging/`. | | `forgelm reject` | Reject a human approval request; the staging directory is preserved for forensics. | | `forgelm approvals` | List pending approvals (`--pending`) or inspect one (`--show RUN_ID`). | -| `forgelm purge` | GDPR Article 17 erasure: row-id, run-id, or `--check-policy` retention report. | -| `forgelm reverse-pii` | GDPR Article 15 right-of-access: search masked corpora for a subject's identifier (plaintext or hash-mask scan). | +| `forgelm purge` | One step of a GDPR Article 17 erasure: row-id, run-id, or `--check-policy` retention report. | +| `forgelm reverse-pii` | An input to a GDPR Article 15 response: search corpora for a subject's identifier (plaintext or hash-mask scan). | | `forgelm cache-models` | Air-gap workflow: pre-populate the HuggingFace Hub cache for one or more models. | | `forgelm cache-tasks` | Air-gap workflow: pre-populate the lm-eval task dataset cache (requires `[eval]` extra). | | `forgelm safety-eval` | Standalone safety evaluation against a model checkpoint (Llama Guard by default). | diff --git a/docs/usermanuals/en/reference/configuration.md b/docs/usermanuals/en/reference/configuration.md index ae2a455b..f997f809 100644 --- a/docs/usermanuals/en/reference/configuration.md +++ b/docs/usermanuals/en/reference/configuration.md @@ -359,8 +359,8 @@ defaults was not recorded. ```yaml retention: - audit_log_retention_days: 1825 # 5 years default (Article 5(1)(e)) - staging_ttl_days: 7 # days to retain staging model after forgelm reject + audit_log_retention_days: 1825 # 5 years: ForgeLM default (project choice; GDPR Article 5(1)(e) sets no fixed period) + staging_ttl_days: 7 # staging dir age that purge --check-policy reports as overdue ephemeral_artefact_retention_days: 90 # compliance bundles, audit reports raw_documents_retention_days: 90 # ingested PDF/DOCX/EPUB/TXT/Markdown enforce: "log_only" # reserved: no command acts on it yet (log_only | warn_on_excess | block_on_excess) diff --git a/docs/usermanuals/en/training/lora.md b/docs/usermanuals/en/training/lora.md index b3e4b05c..898c5a18 100644 --- a/docs/usermanuals/en/training/lora.md +++ b/docs/usermanuals/en/training/lora.md @@ -94,14 +94,13 @@ LoRA only trains the modules you point it at. Defaults are reasonable for most a ```yaml lora: - target_modules: "all-linear" # broadest — every Linear layer - # or specifically: - target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] - # or just attention: + # broader — attention and MLP projections (a list; the string "all-linear" is not accepted): + # target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] + # or just the query and value projections (the default): target_modules: ["q_proj", "v_proj"] ``` -Broader = more capacity but more VRAM. The default (Q/K/V/O projections) is the right balance for most tasks. +Broader = more capacity but more VRAM. The default (`["q_proj", "v_proj"]`, the query and value projections) is the right balance for most tasks. ## DoRA — when it pays off diff --git a/docs/usermanuals/tr/compliance/gdpr-erasure.md b/docs/usermanuals/tr/compliance/gdpr-erasure.md index 77e6b09a..6c3eba3f 100644 --- a/docs/usermanuals/tr/compliance/gdpr-erasure.md +++ b/docs/usermanuals/tr/compliance/gdpr-erasure.md @@ -1,22 +1,22 @@ --- title: GDPR Silme & Erişim -description: `forgelm purge` ve `forgelm reverse-pii` subcommand'ları ile Madde 15 (erişim hakkı) ve Madde 17 (silinme hakkı) taleplerini karşılayın. +description: `forgelm purge` ve `forgelm reverse-pii` subcommand'ları ile Madde 15 (erişim) ve Madde 17 (silme) taleplerini destekleyin. --- # GDPR Silme & Erişim -ForgeLM, en yaygın iki GDPR veri-sahibi hakkı için kardeş subcommand'lar ship eder: `forgelm purge` (Madde 17 — silinme hakkı) ve `forgelm reverse-pii` (Madde 15 — erişim hakkı). Per-output-dir salt'ı paylaşırlar; böylece compliance reviewer aynı veri sahibi için silme ve erişim taleplerini tek bir tamper-evident audit zincirinde — cleartext identifier operatör terminalinden hiç çıkmadan — korele edebilir. +ForgeLM, iki yaygın GDPR veri-sahibi talebini destekleyen kardeş subcommand'lar ship eder: `forgelm purge` satır kaldırır (Madde 17 silmenin bir adımı) ve `forgelm reverse-pii` satır bulur (Madde 15 yanıtının bir girdisi). Hiçbiri tek başına eksiksiz bir yanıt değildir. Per-output-dir salt'ı paylaşırlar; böylece compliance reviewer aynı veri sahibi için silme ve erişim taleplerini tek bir tamper-evident audit zincirinde — cleartext identifier operatör terminalinden hiç çıkmadan — korele edebilir. Bu sayfa operatör hızlı-referansıdır. Daha derin deployer akışı [GitHub'daki GDPR silme deployer kılavuzunda](https://github.com/HodeTech/ForgeLM/blob/main/docs/guides/gdpr_erasure-tr.md) bulunur; flag-başına referanslar manuel içindeki [CLI Referansı](#/reference/cli) ve GitHub'daki özel subcommand sayfaları [`purge_subcommand-tr.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/reference/purge_subcommand-tr.md) + [`reverse_pii_subcommand-tr.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/reference/reverse_pii_subcommand-tr.md) sayfalarındadır. ## İki hak, tek zincir -| Hak | Subcommand | Ne yanıtlar | +| Talep | Subcommand | Ne yapar | |---|---|---| -| **Madde 15** — erişim hakkı | `forgelm reverse-pii` | "Hakkımda kayıt tutuyor musunuz?" — JSONL corpus'ları subject identifier'ı için tarar. | -| **Madde 17** — silinme hakkı | `forgelm purge` | "Hakkımdaki her kaydı silin." — bir satırı, bir staging dizinini veya bir compliance bundle'ını atomik olarak siler. | +| **Madde 15** — erişim | `forgelm reverse-pii` | Taradığınız JSONL corpus'larında veri sahibinin identifier'ını içeren satırları bulur: yanıtın bir girdisidir, yanıtın kendisi değil. | +| **Madde 17** — silme | `forgelm purge` | Bir corpus satırını id ile atomik olarak siler (silmenin bir adımı, tamamı değil); `--run-id` ile bir staging dizinini veya adı run id'yi embed eden compliance dosyalarını siler. | -İki subcommand da audit satırını kaydetmeden önce subject identifier'ını **aynı** per-output-dir salt (`/.forgelm_audit_salt`) ile hash'ler. `data.access_request_query` üzerindeki `query_hash` ve `data.erasure_*` üzerindeki `target_id` aynı subject için byte-byte aynıdır; reviewer bu sayede ikisini cleartext olmadan korele edebilir. +İki subcommand da audit satırını kaydetmeden önce veri sahibinin identifier'ını **aynı** per-output-dir salt (`/.forgelm_audit_salt`) ile hash'ler. `data.access_request_query` üzerindeki `query_hash` ve `data.erasure_*` üzerindeki `target_id` yalnızca `--row-id` değeri ile `--query` değeri aynı metinse, iki komut da aynı `--output-dir`'i kullanıyorsa ve `FORGELM_AUDIT_SECRET` ikisinde de aynı durumdaysa byte-byte aynıdır; aksi halde ikisini `forgelm purge --justification`'a verdiğiniz ticket referansıyla bağlayın. ## Madde 17 — `forgelm purge` @@ -29,7 +29,7 @@ forgelm purge --row-id "ali@example.com" --corpus train.jsonl \ # Koşum-kapsamlı silme forgelm purge --run-id fg-abc123def456 --kind staging --output-dir ./outputs -forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs +forgelm purge --run-id fg-abc123def456 --kind artefacts --output-dir ./outputs # standart pakette hiçbir şey bulmaz, aşağıya bakın # Retention-policy raporu (salt-okunur) forgelm purge --check-policy --config configs/run.yaml --output-dir ./outputs @@ -44,13 +44,16 @@ Audit zincirine altı event yazılır (bkz. [GitHub'daki Audit Event Kataloğu]( - `data.erasure_failed` — disk operasyonu raise ettiğinde veya eşleşen satır/koşum bulunmadığında bunun yerine yayılır. - `data.erasure_warning_memorisation` — corpus-satır silme × `--output-dir` altında doğrudan bir `final_model/` mevcut (satır diskten gitti ama eğitilmiş ağırlıklarda hâlâ memorize edilmiş olabilir). - `data.erasure_warning_synthetic_data_present` — corpus-satır silme × `output_dir`'de `synthetic_data*.jsonl` mevcut. -- `data.erasure_warning_external_copies` — yüklü config boş-olmayan `webhook` block'u içeriyor; downstream tüketiciler bildirim almış olabilir. +- `data.erasure_warning_external_copies` — yalnızca corpus-satır silmede ve yalnızca `--config` verdiğinizde: config'in `webhook` bloğu boş değil (literal bir `url` ya da purge ortamında export edilmiş bir `url_env` değişkeni); downstream tüketiciler bildirim almış olabilir. ### Neyi YAPMAZ - **Modelleri yeniden eğitmez.** Bir satırı corpus'tan kaldırmak onu zaten eğitilmiş ağırlıklardan unutturmaz — silinmiş satır olmadan tam yeniden eğitim tek doğru çözümdür. Tool `data.erasure_warning_memorisation` yayar; böylece boşluk görünür olur. -- **Audit log'unu silmez.** Madde 17(3)(b) audit / muhasebe kayıtlarını hukuki yükümlülük savunması olarak korur; tool silmeyi geçmişi yeniden yazmak yerine yeni bir event olarak kaydeder. -- **Downstream tüketicilere bildirim göndermez.** Webhook receiver'ları, dataset mirror'ları ve deploy edilmiş model endpoint'leri operatörün runtime-katmanı sorumluluğudur (Madde 17(2)). `data.erasure_warning_external_copies` event'ı açık hatırlatmadır. +- **Audit log'unu silmez.** Tool, silmeyi geçmişi yeniden yazmak yerine yeni bir event olarak kaydeder ve audit log'dan hiçbir girdiyi silmez. Veri sahibini adlandıran girdilerin tutulup tutulamayacağı veri sorumlusunun kararıdır ve belgelenmelidir. Madde 17(1)'deki silme nedenlerinden biri geçerliyse girdiler yalnızca bir Madde 17(3) istisnasının kapsadığı yerde kalır: 17(3)(b), yalnızca işlemenin veri sorumlusunun tabi olduğu AB veya üye devlet hukukundan doğan bir yasal yükümlülüğe uyum için (veya kamu yararına ya da kamu otoritesinin kullanılmasına ilişkin bir görev için) gerekli olduğu ölçüde uygulanır; 17(3)(e) ise hukuki taleplerin ileri sürülmesini, kullanılmasını veya savunulmasını kapsar. Madde 17(1)'deki nedenlerin hiçbiri geçerli değilse (girdilerin toplandıkları amaç için hâlâ gerekli olması yalnızca 17(1)(a)'yı dışlar; rızanın geri çekilmesini, Madde 21 itirazını ve hukuka aykırı işlemeyi de kontrol edin) silme gerekmez; ancak talebe yine de gecikmeksizin ve en geç talebin alınmasından itibaren bir ay içinde yanıt verilir; yanıtta gerekçe, bir denetim otoritesine şikâyet etme ve yargı yoluna başvurma imkânı bildirilir (Madde 12(4)). +- **İşaretsiz legacy `final_model.staging/` dizinini silmez.** `--kind staging`, `final_model.staging./` dizinini siler; legacy `final_model.staging/` dizinini yalnızca içinde run id'ye eşit bir `staging_run_id` dosyası varsa siler. ForgeLM bu işaretçiyi yazmaz, bu yüzden işaretsiz bir legacy dizini kendiniz silin. +- **`--kind artefacts` ile standart compliance paketini bulmaz.** Trainer run id içermeyen sabit dosya adları yazar (`compliance_report.json`, `training_manifest.yaml`, ...); resolver ise yalnızca run id'yi embed eden dosya adlarını eşler. Normal bir çıktı dizininde komut hiçbir şey bulmaz, `data.erasure_failed` (`NoMatchingArtefacts`) yayar, 1 ile çıkar ve hiçbir şey silmez. Bu, gerçek bir run id için (`fg-…`) geçerlidir: sabit bir adda geçen genel bir token (örneğin `json` veya `report`) o dosyalarla eşleşir ve onları siler; bu yüzden her zaman tam run id'yi verin. Paketi depolama katmanınız üzerinden kaldırın. +- **Downstream tüketicilere bildirim göndermez.** Webhook receiver'ları, dataset mirror'ları ve deploy edilmiş model endpoint'leri operatörün runtime-katmanı sorumluluğudur (GDPR Madde 19, verinin ifşa edildiği her alıcıya silmeyi bildirmeyi ister; bu, imkânsız olmadıkça veya orantısız çaba gerektirmedikçe geçerlidir ve veri sahibi isterse bu alıcıları ona da bildirmek gerekir; Madde 17(2) ise veri sorumlusu veriyi kamuya açtıysa makul adımlar ekler). `data.erasure_warning_external_copies` event'ı açık hatırlatmadır. +- **Yanıtın tamamını vermez.** Eksiksiz bir yanıt, ForgeLM'in yapmadığı adımları da gerektirir: talep edenin kimliğini makul önlemlerle doğrulamak (Gerekçe 64; Madde 12(6), makul şüphe varsa ek bilgi istemenize izin verir); talebin alınmasından itibaren bir ay içinde yanıtlamak (Madde 12(3); karmaşık veya çok sayıda talep için iki ay daha uzatılabilir, uzatma ilk ay içinde bildirilir); bir erişim talebinde, veri sahibine verinin bir kopyasını ve Madde 15(1)(a)-(h) bilgilerini vermek (Madde 15(3)) ve veri üçüncü bir ülkeye aktarıldıysa Madde 46 güvencelerini (Madde 15(2)) vermek; bunu başkalarının hak ve özgürlüklerini olumsuz etkilemeden yapmak (Madde 15(4)); bir silme talebinde, Madde 17(1)'deki nedenlerden birinin geçerli olup olmadığına ve tuttuğunuz şeyi hangi Madde 17(3) istisnasının (varsa) kapsadığına karar vermek; alıcıları bilgilendirmek (Madde 19); satırların gittiğini doğrulamak için purge sonrasında erişim aramasını yeniden çalıştırmak; ve bir talebi reddederseniz gecikmeksizin ve en geç bir ay içinde gerekçeyi, şikâyet etme ve yargı yoluna başvurma imkânını bildirmek (Madde 12(4)). Eğitilmiş modelin kişisel veri sayılıp sayılmayacağı ayrı bir sorudur (EDPB Görüş 28/2024). ## Madde 15 — `forgelm reverse-pii` @@ -75,7 +78,7 @@ forgelm reverse-pii --query "alice@example.com" --type email \ - `data.access_request_query` — scan'den sonra (veya mid-scan I/O hatası sonrası, `error_*` field'larıyla) yayılır. **Hash'lenmiş** `query_hash`'i, `identifier_type`'ı, `scan_mode`'u, `files_scanned` listesini, `match_count`'u ve `salt_source`'ü taşır. -Chain satırı cleartext identifier'ı asla taşımaz — salt audit log'un kendisine karşı wordlist saldırılarına karşı korur. +Chain satırı cleartext identifier'ı asla taşımaz, yalnızca salt'lı bir digest taşır. Digest anonim değil, kişisel veridir (Gerekçe 26); ek bilgi ayrı tutulup kendi kontrolleri altında korunuyorsa takma adlandırma (pseudonymisation, Madde 4(5)) sayılır. Varsayılan olarak salt dosyası log'un yanında durur; ikisini de okuyabilen biri tahmin ettiği bir identifier'ı onunla sınayabilir: `FORGELM_AUDIT_SECRET` ayarlayın (salt'a XOR'lanır) ve log'u ile salt'ı kişisel veri gibi koruyun. ### `--type custom` regex caveat @@ -87,21 +90,21 @@ Chain satırı cleartext identifier'ı asla taşımaz — salt audit log'un kend İki primitif kasıtlı olarak ayrıdır: -- `FORGELM_AUDIT_SECRET` rotasyonu **yalnızca chain HMAC anahtarını** rotate eder. Identifier hash'leri per-output-dir salt'tan türetilir ve **env-var rotasyonlarına karşı stabildir**; rotasyondan sonra aynı `--output-dir` üzerinde mount edilen bir `forgelm reverse-pii` sorgusu, rotasyondan ÖNCE kaydedilmiş `forgelm purge` event'leriyle hâlâ korele eder. +- `FORGELM_AUDIT_SECRET` rotasyonu chain HMAC anahtarını **ve**, değişken ayarlıysa, identifier-hash salt'ını rotate eder: `forgelm purge` secret'ın ilk 16 byte'ını per-output-dir salt'a XOR'lar ve `salt_source=env_var` kaydeder; bu yüzden `target_id` digest'leri secret ile değişir. Düz-metin bir `forgelm reverse-pii` sorgusu (`--salt-source` yok) salt'ı aynı şekilde çözer; bu yüzden aynı secret durumunda koşturulmuş bir purge ile korele eder. `--salt-source` farklı bir özelliktir: identifier'ları ForgeLM dışında bu output dizininin salt'ıyla hash'lenmiş corpus'lar için hash-mask taramasıdır ve düz metin içeren bir corpus'ta, kişi orada olsa bile sıfır eşleşme bildirir. - Birini incelemek diğerini ifşa etmez. -- Per-output-dir salt, identifier hash'lemenin tek girdisidir; output dizininin yaşam süresi boyunca kalıcıdır ve chain HMAC'in rotasyon kadansını cross-tool digest korelasyonundan izole eder. +- Per-output-dir salt identifier hash'lemenin her zaman bir girdisidir ve output dizininin yaşam süresi boyunca kalıcıdır; secret, ayarlıysa, ikinci girdidir. ## Exit kodları -İki subcommand da projenin `0/1/2/3/4` sözleşmesine uyar: +İki subcommand da projenin herkese açık exit-code sözleşmesinin (0-6) ilk üç kodunu kullanır: | Kod | Anlamı | |---|---| -| 0 | Başarı. İhlal bulunduğunda bile `forgelm purge --check-policy` 0 ile çıkar (report-not-gate semantik). Eşleşme bulunmadığında bile `forgelm reverse-pii` 0 ile çıkar (Madde 15 "kayıt yok"u geçerli yanıt olarak açıkça kabul eder). | -| 1 | Config hatası: kötü flag'ler, okunamaz corpus, hatalı regex, çakışan retention değerleri, yazılamaz `--audit-dir`. | -| 2 | Runtime hatası: I/O başarısızlığı, izin reddedildi, ReDoS SIGALRM timeout, atomic-rename başarısızlığı. | +| 0 | Başarı. İhlal bulunduğunda bile `forgelm purge --check-policy` 0 ile çıkar (report-not-gate semantik). Eşleşme bulunmadığında bile `forgelm reverse-pii` 0 ile çıkar (sıfır eşleşme yalnızca literal metnin taradığınız dosyalarda olmadığını söyler; kişi hakkında veri tutmadığınızı tek başına doğrulamaz). | +| 1 | Config hatası: eksik `--kind`, `--corpus`'suz `--row-id`, hatalı regex, `--run-id` ile eşleşen staging dizini / compliance dosyası yok, yüklenemeyen bir `--config`. | +| 2 | Runtime hatası: I/O başarısızlığı, izin reddedildi, yazılamaz `--audit-dir`, argüman-parser kullanım hatası (birlikte verilen iki mod, `--kind logs`), ReDoS SIGALRM timeout, atomic-rename başarısızlığı. | -Kod 3 (`EXIT_EVAL_FAILURE`) ve 4 (`EXIT_AWAITING_APPROVAL`) iki subcommand'ın da yüzeyinin parçası değildir. +Kod 3 ile 6 arası (`EXIT_EVAL_FAILURE`, `EXIT_AWAITING_APPROVAL`, `EXIT_WIZARD_CANCELLED`, `EXIT_INTEGRITY_FAILURE`) iki subcommand'ın da yüzeyinin parçası değildir. ## Sık hatalar @@ -118,7 +121,7 @@ Kod 3 (`EXIT_EVAL_FAILURE`) ve 4 (`EXIT_AWAITING_APPROVAL`) iki subcommand'ın d ::: :::tip -**`forgelm verify-audit`'i DSAR kapanışınıza wire'layın.** Erasure / access event zincire girdikten sonra `forgelm verify-audit /audit_log.jsonl --require-hmac` çalıştırın ve doğrulama çıktısını DSAR ticket'a iliştirin. Auditor okuyacak. +**`forgelm verify-audit`'i DSAR kapanışınıza wire'layın.** Erasure / access event zincire girdikten sonra `forgelm verify-audit /audit_log.jsonl` çalıştırın ve doğrulama çıktısını DSAR ticket'a iliştirin. `--require-hmac`'i yalnızca zincir yazılırken `FORGELM_AUDIT_SECRET` ayarlıysa ve şimdi de ayarlıysa ekleyin: bayrak secret olmadan 1 ile çıkar ve herhangi bir satırda HMAC etiketi yoksa 6 ile çıkar; yani secret olmadan yazılmış bir zincirde tasarım gereği başarısız olur. Auditor okuyacak. ::: ## Bkz. diff --git a/docs/usermanuals/tr/compliance/gdpr.md b/docs/usermanuals/tr/compliance/gdpr.md index 152a5c50..b28bff59 100644 --- a/docs/usermanuals/tr/compliance/gdpr.md +++ b/docs/usermanuals/tr/compliance/gdpr.md @@ -19,7 +19,7 @@ GDPR (Avrupa) ve KVKK (Türkiye), eğitimde kullanılan kişisel verilere koruma | **Bütünlük & gizlilik** | GDPR Md. 5(1)(f) | Yetkisiz erişime karşı koruyun. | | **Hesap verebilirlik** | GDPR Md. 5(2) | Uyumu gösterebilmelisiniz. | -KVKK (Türkiye) bu ilkeleri yakından yansıtır. +KVKK (Türkiye, 6698 sayılı Kanun) benzer ilkeleri 4. maddesinde sayar. 11. madde ilgili kişiye verilerinin işlenip işlenmediğini öğrenme ve silinmesini ya da yok edilmesini isteme hakkını tanır (11/1-(e); 7. maddedeki koşullar çerçevesinde, 7. madde ayrıca işleme sebebi ortadan kalkınca veri sorumlusunu verileri silmeye, yok etmeye veya anonim hale getirmeye mecbur eder) ve veri sorumlusu başvuruyu en geç otuz gün içinde sonuçlandırır (13/2). KVKK'da ayrı bir hesap verebilirlik ilkesi yoktur ve güvenlik yükümlülükleri 12. maddededir. ## ForgeLM her birini nasıl ele alıyor @@ -31,7 +31,7 @@ Ingest'te PII maskeleme, ForgeLM'in tespit ettiği tanımlayıcı biçimlerini b $ forgelm ingest ./policies/ --recursive --strategy markdown --pii-mask --output data/policies.jsonl ``` -Dedektörler e-posta, telefon, kredi kartı, IBAN ile Türk, Alman, Fransız ve ABD ulusal kimlik biçimlerini kapsar; her eşleşme `[REDACTED]` olur. **İsimler, adresler, doğum tarihleri, sağlık verisi ve diğer serbest metin tespit edilmez**; desenler sıradan düzyazıyı maskelemek yerine sınırda bir eşleşmeyi kaçırmaya ayarlıdır. Maskeleme JSONL'daki kişisel veriyi azaltır; tanımlanabilir özneleri ondan kaldırmaz. Bkz. [PII Maskeleme](#/data/pii-masking). +Dedektörler e-posta, telefon, kredi kartı, IBAN ile Türk, Alman, Fransız ve ABD ulusal kimlik biçimlerini kapsar; her eşleşme `[REDACTED]` olur. **İsimler, adresler, doğum tarihleri, sağlık verisi ve diğer serbest metin tespit edilmez**; desenler sıradan düzyazıyı maskelemek yerine sınırda bir eşleşmeyi kaçırmaya ayarlıdır. Maskeleme JSONL'daki kişisel veriyi azaltır; tanımlanabilir özneleri ondan kaldırmaz. Geriye kaç isim ve yerin (bazı dil modellerinde kurumların da) kaldığını ölçmek için `forgelm audit --pii-ml` çalıştırın: Presidio NER **tespiti** ekler; bu tespit her split için kategori başına sayıları raporlar (hangi satırların ya da hangi isimlerin olduğunu değil) ve hiçbir şeyi maskelemez. `ingestion-pii-ml` extra'sını ve bir spaCy modelini gerektirir (Türkçe gibi özel modeli olmayan diller için `--pii-ml-language xx`, `xx_ent_wiki_sm` kullanır). Bilinen bir ismi bulmak için onu `forgelm reverse-pii` ile arayın. Bkz. [PII Maskeleme](#/data/pii-masking) ve [ML-NER PII](#/data/pii-ml). ### Hesap verebilirlik (Md. 5(2)) @@ -53,7 +53,7 @@ retention: ephemeral_artefact_retention_days: 365 ``` -`forgelm purge --check-policy --config config.yaml` ufku aşmış artefaktları listeler ve her zaman 0 ile çıkar. Hiçbir şey otomatik silinmez ve bunun için audit log'a bir şey yazılmaz; süresi geçeni silmek depolama katmanınızın işidir. (`retention.enforce` ayrılmıştır: henüz hiçbir komut onu kullanmaz.) +`forgelm purge --check-policy --config config.yaml --output-dir ` o dizinde ufku aşmış artefaktları listeler ve kaç tane bulursa bulsun 0 ile çıkar (`--output-dir` verilmezse `training.output_dir`'i değil, bulunulan dizini tarar). Hiçbir şey otomatik silinmez ve bunun için audit log'a bir şey yazılmaz; süresi geçeni silmek depolama katmanınızın işidir. (`retention.enforce` ayrılmıştır: henüz hiçbir komut onu kullanmaz.) ForgeLM `raw_documents/` veya `ingestion_output/` dizinlerini oluşturmaz; bu yüzden `raw_documents_retention_days`, yalnızca kaynak dokümanları `/raw_documents/` ya da `ingestion_output/` altında tutuyorsanız etkili olur. ## Veri sahibi talepleri @@ -66,17 +66,18 @@ En sık talep tipleri ve ForgeLM nasıl yardım eder: Kişinin tanımlayıcısını eğitim verinizde arayın: ```shell -$ forgelm reverse-pii --query "ali@example.com" data/*.jsonl +$ forgelm reverse-pii --query "ali@example.com" --output-dir ./outputs data/*.jsonl +No matches for identifier hash 3f2a9c1e5b7d4a60… (literal, plaintext mode) in 3 file(s). ``` -Tarama, tanımlayıcının geçtiği satırları raporlar; sıfır eşleşme geçerli bir cevaptır. Yalnızca işaret ettiğiniz dosyalarda gerçekten bulunanı bulur. Maskeleme yalnızca tespit ettiği tanımlayıcı biçimlerini kaldırır; bir isim ya da tespit edilemeyen bir varyant kalabilir ve maskelenmiş JSONL taraması ham kaynak dokümanları kapsamaz: onları ayrıca arayın. ForgeLM eğitilmiş bir modelin neyi hatırladığını söyleyemez. +Tarama, tanımlayıcının geçtiği dosyayı ve satırı raporlar (snippet'ler ham corpus metnidir; kalıcı bir log'a yönlendirmeyin); yukarıdaki gibi sıfır eşleşme yalnızca, taradığınız dosyaların hiçbirinde birebir metnin bulunmadığı anlamına gelir. Tek başına kişi hakkında hiç veri tutmadığınızı göstermez ve Madde 15 yanıtı bir arama sonucundan fazlasını ister: verinin bir kopyası ve Madde 15(1)(a)-(h) bilgileri (bkz. [GDPR Silme ve Erişim](#/compliance/gdpr-erasure)). Basılan değer, audit event'inin kaydettiği salt'lı tanımlayıcı hash'inin ilk 16 hex hanesidir, tanımlayıcının kendisi değildir. `--output-dir`, ForgeLM'in dizin başına salt'ı (`.forgelm_audit_salt`) tuttuğu ve `data.access_request_query` event'ini eklediği yerdir: `forgelm purge`'e verdiğiniz dizini verin, böylece iki event aynı zincire katılır. Verilmezse ikisi de ilk corpus dosyasının yanına yazılır ve ForgeLM bir uyarı loglar; `data/*.jsonl` gibi bir glob ile bu, `audit_log.jsonl`'i corpus dosyalarınızın arasına koyar, bu yüzden her zaman `--output-dir` verin. Yalnızca işaret ettiğiniz dosyalarda gerçekten bulunanı bulur: eşleşme büyük/küçük harfe duyarlıdır ve ham dosya metnini karşılaştırır, bu yüzden yazım ve harf varyantlarını da arayın (`--type custom --query '(?i)…'`); `\u00e7` gibi ASCII kaçışlarıyla yazılmış bir corpus ASCII dışı isimleri literal sorgudan gizler. Maskeleme yalnızca tespit ettiği tanımlayıcı biçimlerini kaldırır; bir isim ya da tespit edilemeyen bir varyant kalabilir ve maskelenmiş JSONL taraması ham kaynak dokümanları kapsamaz: onları ayrıca arayın. ForgeLM eğitilmiş bir modelin neyi hatırladığını söyleyemez. ### Silme hakkı (Md. 17) "Verilerimi silin." Bir kişinin verisi eğitim setinizdeydi: -1. Satırları `forgelm reverse-pii` ile bulun, sonra `forgelm purge --row-id --corpus ` ile kaldırın; her silme audit log'a yazılır. `--row-id` satırın `id` veya `row_id` alanıyla eşleştirilir, satır numarasıyla asla; `forgelm ingest` bu alanlardan hiçbirini yazmaz. Bu yüzden **eğitimden önce** her satıra kalıcı bir id verin (aşağıya bakın). Id'si olmayan bir corpus satır satır silinemez. +1. Satırları `forgelm reverse-pii` ile bulun, sonra `forgelm purge --row-id --corpus --output-dir ./outputs` ile kaldırın (`reverse-pii` ile aynı dizin; purge'ün eğitilmiş bir `final_model/` için de uyarabilmesi adına eğitim çıktı dizininizi kullanın); her silme audit log'a yazılır. `--row-id` satırın `id` veya `row_id` alanıyla eşleştirilir, satır numarasıyla asla; `forgelm ingest` bu alanlardan hiçbirini yazmaz. Bu yüzden **eğitimden önce** her satıra kalıcı bir id verin (aşağıya bakın). Id'si olmayan bir corpus satır satır silinemez. 2. Ham kaynak dokümanlar hâlâ içerebilir — ham deponuzdan düşürün ve gerekirse yeniden ingest edin. 3. Model bazı detayları ezberlemiş olabilir — aşağıdaki "model-seviyesi silme"ye bakın. @@ -106,11 +107,11 @@ finally: os.remove(tmp) ``` -Eğitimi id'li dosyadan yapın. `forgelm reverse-pii` tanımlayıcının geçtiği dosyayı ve satırı raporlar; o satırın `id` değerini dosyadan okuyup `forgelm purge --row-id`'ye verin. Eğitimden sonra id'leri yeniden atamak audit log ile corpus arasındaki bağı koparır; bu yüzden bir kez atayın. +Eğitimi id'li dosyadan yapın. `forgelm reverse-pii` tanımlayıcının geçtiği dosyayı ve satırı raporlar; o satırın `id` değerini dosyadan okuyup `forgelm purge --row-id`'ye verin. Eğitimden sonra id'leri yeniden atamak audit log ile corpus arasındaki bağı koparır; bu yüzden bir kez atayın. Yeni dosyayı kontrol ettikten sonra özgün `data/train.jsonl`'i taşıyın ya da silin: id'siz olarak hâlâ her satırı tutar; bu yüzden `data/*.jsonl` glob'u iki kopyayı da eşleştirir, `reverse-pii` kişiyi ikisinde de raporlar ve `purge` yalnızca `--corpus` ile adını verdiğiniz dosyayı düzenler. ### Model-seviyesi silme -LLM'ler eğitimden nadir string'leri ezberleyebilir. PII maskelemeyle bile deploy edilmiş bir modelden tüm izleri kaldırmak zordur. ForgeLM'in savunmaları: +LLM'ler eğitimden nadir string'leri ezberleyebilir. PII maskelemeyle bile deploy edilmiş bir modelden tüm izleri kaldırmak zordur. EDPB Görüş 28/2024'e göre kişisel veriyle eğitilmiş bir model her zaman anonim değildir; modelinizin anonim olup olmadığı olay bazında değerlendirilmelidir. Anonim olduğunu gösterene kadar erişim ve silme taleplerine vereceğiniz yanıtları, eğitilmiş ağırlıklar hâlâ kişisel veri içerebilirmiş gibi planlayın. ForgeLM'in savunmaları: - **Girenleri azalt:** PII maskeleme ve deduplikasyon (ezberlenen veri genelde tekrarlanan veridir). - **Ezber ölçülmez:** ForgeLM, eğitilmiş bir modelin belirli bir string'i üretip üretmediğini test etmez. diff --git a/docs/usermanuals/tr/compliance/human-oversight.md b/docs/usermanuals/tr/compliance/human-oversight.md index 98bbbe15..1d7cc871 100644 --- a/docs/usermanuals/tr/compliance/human-oversight.md +++ b/docs/usermanuals/tr/compliance/human-oversight.md @@ -95,7 +95,7 @@ ForgeLM yerleşik bir quorum gate'i **göndermez**. `approval.quorum` alanı yok ## Timeout -ForgeLM staged koşumları **timeout etmez**. `approval.timeout_hours`, `human_approval.timeout` event'i veya auto-fail saati yoktur. Staged koşum süresiz bekler; deployer eski staging dizinlerinin sona ermesini istiyorsa bunun yerine `retention.staging_ttl_days`'i konfigüre edin — bu GDPR Madde 17 retention pipeline'ına bağlanır (yapılandırılan horizon'dan sonra dizini fiziksel olarak prune eder, approval gate'e değil). +ForgeLM staged koşumları **timeout etmez**. `approval.timeout_hours`, `human_approval.timeout` event'i veya auto-fail saati yoktur. Staged koşum süresiz bekler. `retention.staging_ttl_days` bunu değiştirmez: yalnızca `forgelm purge --check-policy`'nin horizon'dan eski staging dizinlerini listelemesini sağlar. Hiçbir şey otomatik silinmez ya da sona erdirilmez ve rapor approval gate'e dokunmaz. Bir staging dizinini kendiniz kaldırmak (`forgelm purge --run-id --kind staging`) bekleyen modeli atar ve onayı karara bağlanmamış bırakır; kararın audit log'da görünmesini istiyorsanız önce bir `forgelm reject` kaydedin. ## Bekleyen koşuları inceleme @@ -141,7 +141,7 @@ Granted / rejected bir koşu üzerinde `--show` çalıştırıldığında tam za ::: :::warn -**Üretim kararları için quorum yok.** Yüksek-riskli üretim deployment'ları için tek-reviewer onayı yetersizdir. Her zaman quorum >= 2 isteyin. +**Üretim kararları için quorum yok.** ForgeLM her karar için tek bir onaylayıcı kaydeder. Risk değerlendirmeniz birden fazla reviewer gerektiriyorsa bunu yukarıda anlatıldığı gibi ForgeLM'in dışında zorlayın (EU AI Act Madde 14(5), bir tanımlamanın en az iki gerçek kişi tarafından ayrıca doğrulanmasını yalnızca Ek III'ün 1(a) bendindeki uzaktan biyometrik tanımlama sistemleri için ister; kolluk, göç, sınır kontrolü ve iltica alanında, Birlik veya ulusal hukuk bunu orantısız bulursa istisna vardır). ::: :::tip diff --git a/docs/usermanuals/tr/compliance/model-card.md b/docs/usermanuals/tr/compliance/model-card.md index 68b486f9..2c33b18e 100644 --- a/docs/usermanuals/tr/compliance/model-card.md +++ b/docs/usermanuals/tr/compliance/model-card.md @@ -86,7 +86,7 @@ model: name_or_path: meta-llama/Llama-3.1-8B-Instruct max_length: 2048 load_in_4bit: true - ... + # ... ``` ## Usage diff --git a/docs/usermanuals/tr/data/audit.md b/docs/usermanuals/tr/data/audit.md index 68bc1c3d..db6ea652 100644 --- a/docs/usermanuals/tr/data/audit.md +++ b/docs/usermanuals/tr/data/audit.md @@ -137,7 +137,7 @@ Yetkili kaynak: `forgelm/cli/_parser.py::_add_audit_subcommand`. | `--quality-filter` / `--no-quality-filter` | Heuristik kalite kontrolleri (mean word length, alphabetic-character ratio, end-of-line punctuation ratio, repeated-line ratio, short-paragraph ratio). **v0.6.0'dan itibaren default-AÇIK** (Faz 15 Görev 5); öncesinde opt-in idi. Atlamak için `--no-quality-filter` geçirin. Rapora `quality_summary` ekler. Bkz. [Kalite Filtresi](#/data/quality-filter). | | `--croissant` | Raporun `croissant` anahtarı altına bir [Google Croissant 1.0](https://mlcommons.org/croissant/) dataset card emit eder. Bkz. [Croissant 1.0 Dataset Kartı](#/data/croissant-card). | | `--pii-ml` | Regex detector'ın üzerine Presidio'nun ML-NER PII tespitini katmanlar. Opsiyonel `forgelm[ingestion-pii-ml]` extra'sını **ve** bir spaCy NER modelini (örn. `python -m spacy download en_core_web_lg`) gerektirir. Bkz. [ML-NER PII (Presidio)](#/data/pii-ml). | -| `--pii-ml-language LANG` | `--pii-ml` için spaCy NLP dili kodu (varsayılan `en`). Türkçe corpus için örn. `tr` set edin VE eşleşen spaCy modelinin kurulu olduğundan emin olun. | +| `--pii-ml-language LANG` | `--pii-ml` için spaCy NLP dili kodu (varsayılan `en`). Örn. Almanca için `de`; Türkçe gibi kendine ait spaCy modeli olmayan bir dil için `xx` (çok dilli `xx_ent_wiki_sm` fallback'i) set edin; eşleşen spaCy modeli kurulu olmalıdır. | | `--workers N` | Split-düzeyi pipeline için worker process sayısı (varsayılan 1, sıralı). Multi-split corpus'larda 2-4'e set ederek near-linear hızlanma sağlanır. Audit JSON worker sayısından bağımsız byte-identical (determinism contract). | | `--output-format {text,json}` | Stdout renderer. `json` modu makine-okunabilir bir özet, `text` varsayılan insan-okunabilir formdur. | | `--allow-secrets` | Credential bulgularını koşuyu başarısız kılmadan kaydeder (`3` yerine bir `SUPPRESSED` uyarısıyla exit `0`). Taramanın kendisi devre dışı bırakılamaz. | diff --git a/docs/usermanuals/tr/data/ingestion.md b/docs/usermanuals/tr/data/ingestion.md index caa97460..63421c4e 100644 --- a/docs/usermanuals/tr/data/ingestion.md +++ b/docs/usermanuals/tr/data/ingestion.md @@ -125,7 +125,7 @@ Yetkili liste için `forgelm ingest --help`. En sık görülenler: ::: :::tip -**Ingest'ten sonra her zaman audit yapın.** Temiz ingest temiz dataset anlamına gelmez. Split-arası sızıntı, near-duplicate ve maskelemenin kaçırdığı PII için `forgelm audit data/output.jsonl` koşturun. +**Ingest'ten sonra her zaman audit yapın.** Temiz ingest temiz dataset anlamına gelmez. Split-arası sızıntı, near-duplicate ve maskelemenin kapsamadığı PII için `forgelm audit data/output.jsonl` koşturun (`forgelm audit` aynı regex'leri yeniden uygular; isim ve konumları saymak için `--pii-ml` ekleyin). ::: ## Bkz. diff --git a/docs/usermanuals/tr/data/pii-masking.md b/docs/usermanuals/tr/data/pii-masking.md index 8683adc0..e5f2d4ed 100644 --- a/docs/usermanuals/tr/data/pii-masking.md +++ b/docs/usermanuals/tr/data/pii-masking.md @@ -5,7 +5,7 @@ description: E-posta, telefon, kredi kartı, IBAN ve ulusal kimlikleri ingest s # PII Maskeleme -Eğitim setinizdeki kişisel veri hem regülatif tehlikedir (GDPR Md. 5(1)(c) — veri minimizasyonu) hem operasyonel tehlikedir (model ezberler ve geri verir). ForgeLM'in PII maskeleyicisi dokuz kategori PII'yi tespit eder ve ingest zamanında, satırlar JSONL'a düşmeden redakte eder. +Eğitim setinizdeki kişisel veri hem regülatif tehlikedir (GDPR Md. 5(1)(c) — veri minimizasyonu) hem operasyonel tehlikedir (model ezberler ve geri verir). ForgeLM'in PII maskeleyicisi sekiz kategori PII'yi tespit eder ve ingest zamanında, satırlar JSONL'a düşmeden redakte eder. ## Tespit edilenler @@ -15,9 +15,9 @@ Eğitim setinizdeki kişisel veri hem regülatif tehlikedir (GDPR Md. 5(1)(c) | **Telefon** | `+90 532 123 45 67`, `(555) 123-4567` | E.164-uyumlu pattern + locale varyantları | | **Kredi kartı** | `4111-1111-1111-1111` | Issuer öneki (Visa/MC/Amex/Discover/JCB/UnionPay) + Luhn — yalnızca Luhn değil; her IMEI Luhn'dan geçer | | **IBAN** | `TR12 0006 4000 0011 2345 6789 01` | Ülke-bilinçli checksum | -| **Ulusal ID — Türkiye** | 11 haneli TC kimlik | Modulo-10 + modulo-11 checksum | -| **Ulusal ID — Almanya** | Steuer-ID | Format + checksum | -| **Ulusal ID — Fransa** | NIR (sosyal güvenlik) | Format + key validation | +| **Ulusal ID — Türkiye** | 11 haneli TC kimlik | İki modulo-10 kontrol hanesi (10. ve 11.) | +| **Ulusal ID — Almanya** | Kimlik kartı seri numarası (Personalausweis), örn. `T22000129` | Yalnızca şekil: bir harf, 7-8 rakam ve isteğe bağlı bir kontrol karakteri; checksum yok | +| **Ulusal ID — Fransa** | NIR (sosyal güvenlik numarası) | Yalnızca şekil: cinsiyet, yıl, ay, departman, komün ve sıra rakamları ile isteğe bağlı iki haneli anahtar; anahtar doğrulanmaz | | **US SSN** | `123-45-6789` | Format + reserved-block dışlama | Bu sekizi tam kümedir (`forgelm/data_audit/_pii_regex.py` içindeki `_PII_PATTERNS`). **IP adresleri tespit edilmez** — bu sayfanın önceki sürümleri bir "IPv4 / IPv6 (varsayılan kapalı; opt-in)" satırı listeliyordu, ancak böyle bir pattern ve böyle bir opt-in mevcut değil. GDPR değerlendirmeniz IP adreslerini kişisel veri sayıyorsa, bunun için ayrı bir kontrole ihtiyacınız var. @@ -46,7 +46,7 @@ Placeholder dataset boyunca tutarlı; model "şu slot'a maskelenmiş *bir şey* ## Yayınlanan placeholder :::warn -**Kategori başına değil, tek bir placeholder vardır.** Tespit edilen her span — e-posta, telefon, kredi kartı, IBAN, TC kimlik, Steuer-ID, NIR, US SSN — tek bir düz `[REDACTED]` ile değiştirilir. Bu sayfanın önceki sürümleri kategori başına etiketlerden oluşan dokuz satırlık bir tablo yayımlıyordu (`[EMAIL_REDACTED]`, `[PHONE_REDACTED]`, `[IP_REDACTED]`, …). Bu string'lerin hiçbiri kod tabanının hiçbir yerinde mevcut değil. Hangi kategorinin maskelendiğini geri elde etmek için redaksiyon etiketlerini parse eden bir tüketici bunu yapamaz — o bilgi yalnızca audit raporundaki `pii_summary` sayımlarında yaşar. +**Kategori başına değil, tek bir placeholder vardır.** Tespit edilen her span — e-posta, telefon, kredi kartı, IBAN, TC kimlik, Alman kimlik kartı seri numarası, NIR, US SSN — tek bir düz `[REDACTED]` ile değiştirilir. Bu sayfanın önceki sürümleri kategori başına etiketlerden oluşan dokuz satırlık bir tablo yayımlıyordu (`[EMAIL_REDACTED]`, `[PHONE_REDACTED]`, `[IP_REDACTED]`, …). Bu string'lerin hiçbiri kod tabanının hiçbir yerinde mevcut değil. Hangi kategorinin maskelendiğini geri elde etmek için redaksiyon etiketlerini parse eden bir tüketici bunu yapamaz — o bilgi yalnızca audit raporundaki `pii_summary` sayımlarında yaşar. ::: Varsayılan `[REDACTED]`'dır (`mask_pii`'nin `replacement` parametresi). Paralel sır maskeleyici `[REDACTED-SECRET]` kullanır — bkz. [Sır Temizleme](#/data/secrets). Yukarıdaki örnekte telefon pattern'ının numaranın tamamını her zaman tüketmediğine dikkat edin; maskelemeye güvenmeden önce kendi formatlarınıza karşı doğrulayın. @@ -55,8 +55,8 @@ Varsayılan `[REDACTED]`'dır (`mask_pii`'nin `replacement` parametresi). Parale PII regex'leri bilinçli olarak **düşük false-positive oran** için ayarlanır. Sınırda eşleşmeyi atlamayı (false negative) prose'unuzdaki PII olmayan string'i redakte etmeye (false positive) tercih eder. Sebepler: -1. False positive sessizce verinizi bozar — gerçek kelimeleri `[EMAIL_REDACTED]` ile değiştirmek örnekleri mahveder. -2. Audit aşaması maskelemenin kaçırdığını yakalar; satır başına düzeltme veya düşürme kararı sizde. +1. False positive sessizce verinizi bozar — gerçek kelimeleri `[REDACTED]` ile değiştirmek örnekleri mahveder. +2. Maskelenmiş çıktı üzerinde `forgelm audit` koşturmak aynı regex'leri yeniden uygular; bu yüzden o pattern'lerin artık eşleşmediğini doğrular, ama pattern'lerin tarif etmediği tanımlayıcıları (örneğin isimleri) bulamaz — onları `--pii-ml` sayar. 3. Agresif regex'ler gerçek-dünya ML pipeline kesintilerine yol açtı (Phase 11.5 olayı [GitHub'daki katkıda bulunan regex standardında](https://github.com/HodeTech/ForgeLM/blob/main/docs/standards/regex.md) belgelenmiştir). Daha katı tespit gerekirse — örneğin yüksek-stake bir hukuk corpus'u — maskeleyiciyi manuel inceleme adımıyla birleştirin. Regex'leri zorlamayın. @@ -67,20 +67,31 @@ Modifiye etmeden tespit için: ```shell $ forgelm audit data/policies.jsonl -⚠ PII: 18 e-posta, 4 telefon, 2 IBAN (orta seviye) +Data audit summary + Source : data/policies.jsonl + Total samples : 12240 + Splits : train + └─ train: n=12240 + length min=24 max=1180 mean=212.4 p95=640 + languages (top-3): en=12240 + PII : email=18, phone=4 + PII severity : worst tier = medium + by tier : medium=18, low=4 ``` -Audit raporu satır indisleri ve offset'leri listeler; spesifik vakaları inceleyebilirsiniz. +Audit raporu kategori ve split başına sayımları taşır; satır indislerini ya da offset'leri değil. Belirli bir tanımlayıcıyı içeren satırları bulmak için onu `forgelm reverse-pii` ile arayın. -## Locale'ler +## Bölgesel formatlar -| Locale | Telefon | Ulusal ID | Notlar | -|---|---|---|---| -| TR (varsayılan) | E.164 + Türkiye formatları | TC kimlik | En çok ayarlanan. | -| DE | E.164 + Almanya formatları | Steuer-ID | | -| FR | E.164 + Fransa formatları | NIR | | -| US | E.164 + (xxx) xxx-xxxx | Reserved-block dışlamalı SSN | | -| Global | Sadece E.164 | yok | Bilinmeyen locale fallback. | +| Format | Neyi eşleştirir | Checksum doğrulaması | +|---|---|---| +| Telefon (tüm ülkeler) | Tek bir pattern: `+` ülke öneki ya da parantezli alan kodu | Hayır | +| TR ulusal ID | 11 rakam (TC kimlik) | Evet: iki modulo-10 kontrol hanesi (10. ve 11. haneler) | +| DE ulusal ID | Kimlik kartı seri numarası: bir harf, 7-8 rakam, isteğe bağlı bir kontrol karakteri | Hayır | +| FR ulusal ID | NIR: isteğe bağlı iki haneli anahtarla 13 karakter (rakam; Korsika için `2A` / `2B`) | Hayır | +| US SSN | `NNN-NN-NNNN`, reserved block'lar hariç | Hayır | + +Yalnızca kredi kartları, IBAN'lar ve TC kimlik numaraları checksum ile doğrulanır; diğer aileler yalnızca şekle göre eşleştirilir. Bu yüzden bir bulguyu doğrulanmış bir tanımlayıcı değil, gözden geçirilecek bir aday sayın. `forgelm ingest --pii-mask` (veya audit eşdeğeri) tarafından tetiklenen regex-bazlı PII katmanı, yukarıdaki tablodaki tüm diff --git a/docs/usermanuals/tr/data/pii-ml.md b/docs/usermanuals/tr/data/pii-ml.md index 556d7cdc..aa40ed96 100644 --- a/docs/usermanuals/tr/data/pii-ml.md +++ b/docs/usermanuals/tr/data/pii-ml.md @@ -5,9 +5,9 @@ description: Regex PII katmanının üzerine person / organization / location te # ML-NER PII Tespiti — `--pii-ml` -Varsayılan [PII Maskeleme](#/data/pii-masking) katmanı regex-anchored'dır ve GDPR Madde 10'un önemsediği *yapılandırılmış* identifier'ları (email, telefon, IBAN, kredi kartı, ulusal kimlikler) kapsar. Presidio NER, regex'in doğal olarak kaçırdığı *yapılandırılmamış* identifier'ları ekler: kişi isimleri, organizasyon isimleri, coğrafi yerler. +Varsayılan [PII Maskeleme](#/data/pii-masking) katmanı regex-anchored'dır ve yaygın *yapılandırılmış* tanımlayıcıları (e-posta, telefon, IBAN, kredi kartı, ulusal kimlik numaraları) kapsar. Bunlar, kimliği belirli veya belirlenebilir bir gerçek kişiye ilişkin olduklarında GDPR Madde 4(1) kapsamında kişisel veridir; Madde 87 ayrıca üye devletlerin ulusal kimlik numaralarının işlenme koşullarını belirlemesine izin verir. Presidio NER, regex'in doğal olarak kaçırdığı *yapılandırılmamış* identifier'ları ekler: kişi isimleri, organizasyon isimleri, coğrafi yerler. -Bu katman **opt-in**'dir. `--pii-ml` geçilmediğinde varsayılan audit + ingest davranışı değişmez. +Bu katman **opt-in**'dir. `--pii-ml` geçilmediğinde varsayılan audit davranışı değişmez (`forgelm ingest`'in `--pii-ml` flag'i yoktur). ## Ne zaman kullanılır @@ -38,10 +38,16 @@ spaCy modeli yoksa `forgelm audit --pii-ml` hiçbir satır taranmadan **önce** ```shell $ forgelm audit data/customer-letters.jsonl --output ./audit/ --pii-ml -✓ format: instructions (8.400 satır) -⚠ PII: 12 email, 3 telefon, 1 IBAN (regex katmanı) -⚠ PII (ML): 47 person, 18 organization, 9 location (Presidio) - en kötü kat: medium +Data audit summary + Source : data/customer-letters.jsonl + Total samples : 8400 + Splits : train + └─ train: n=8400 + length min=31 max=2210 mean=402.7 p95=1180 + languages (top-3): en=8400 + PII : email=12, location=9, person=47, phone=3 + PII severity : worst tier = medium + by tier : medium=59, low=12 ``` Presidio bulguları aynı `pii_summary` ve `pii_severity` bloklarına disjoint kategori isimleriyle birleşir, böylece regex baseline ML sinyaliyle yan yana görünür kalır: @@ -52,16 +58,17 @@ Presidio bulguları aynı `pii_summary` ve `pii_severity` bloklarına disjoint k "email": 12, "phone": 3, "person": 47, - "organization": 18, "location": 9 }, "pii_severity": { - "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 30}, + "by_tier": {"critical": 0, "high": 0, "medium": 59, "low": 12}, "worst_tier": "medium" } } ``` +Presidio'nun varsayılan İngilizce yapılandırması `organization` etiketini yok sayar (çok yanlış pozitif üretir); bu yüzden varsayılan `--pii-ml-language en` ile `organization` sayısı 0 kalır ve yukarıdaki örnek onu göstermez; bu etiketi raporlayan bir dil modeli (örneğin `--pii-ml-language xx`) bir `organization` sayısı ekler. + ## Şiddet katmanları Yeni kategoriler `forgelm.data_audit.PII_ML_SEVERITY` adında özel bir tabloda yaşar: @@ -115,7 +122,7 @@ _require_presidio() text = "Alice Williams Pazartesi Acme Corp'un Berlin ofisini ziyaret ediyor." counts = detect_pii_ml(text, language="en") print(counts) -# {'person': 1, 'organization': 1, 'location': 1} +# {'person': 1, 'location': 1} (Presidio'nun varsayılan İngilizce yapılandırması organization'ı yok sayar) ``` Fonksiyon non-string input, eksik extra'lar veya geçici analyzer hataları için boş dict döndürür (tek bir kötü satır audit'i bloke etmez). Sert arızalar (eksik spaCy modeli, kayıtlı olmayan dil) `_require_presidio(language=...)` tarafından peşinen yakalanır ve hiçbir satır taranmadan önce raise edilir — `audit_dataset`'i bypass ettiğinizde aynı pre-flight garantilerini istiyorsanız bunu açıkça çağırın. diff --git a/docs/usermanuals/tr/deployment/chat.md b/docs/usermanuals/tr/deployment/chat.md index 9ef6f7ec..a3d91a90 100644 --- a/docs/usermanuals/tr/deployment/chat.md +++ b/docs/usermanuals/tr/deployment/chat.md @@ -11,40 +11,52 @@ description: Akışlı REPL'de fine-tuned modelinizi sınayın. ```shell $ forgelm chat ./checkpoints/customer-support -ForgeLM 0.7.0 — checkpoints/customer-support ile sohbet -forgelm> aboneliği nasıl iptal ederim? -Aboneliğinizi Ayarlar → Faturalandırma → Aboneliği İptal Et adımlarıyla -iptal edebilirsiniz. Erişiminiz mevcut faturalama döneminin sonuna kadar -devam eder… +ForgeLM Chat — type your message and press Enter +Streaming : on | Temperature : 0.7 +Commands : /help /reset /save /temperature /system /exit +──────────────────────────────────────────────────────── -forgelm> /system Türk telekom için kibar bir müşteri-destek temsilcisisin. -[system prompt güncellendi] +> aboneliği nasıl iptal ederim? +Assistant: Aboneliğinizi Ayarlar → Faturalandırma → Aboneliği İptal Et +adımlarıyla iptal edebilirsiniz. Erişiminiz mevcut faturalama döneminin +sonuna kadar devam eder… -forgelm> tarifeyi nasıl yükseltirim? -Tarifenizi Ayarlar → Plan → Yükselt menüsünden değiştirebilirsiniz... +> /system Türk telekom için kibar bir müşteri-destek temsilcisisin. +[System prompt updated] + +> tarifeyi nasıl yükseltirim? +Assistant: Tarifenizi Ayarlar → Plan → Yükselt menüsünden değiştirebilirsiniz... ``` +CLI çıktısı yalnızca İngilizcedir; yukarıdaki banner ve köşeli parantezli mesajlar bu yüzden çevrilmemiştir. `rich` kuruluysa banner bir panel içinde çizilir. Yeni bir system prompt sonraki turlara uygulanır; o ana kadarki konuşmayı yeniden yazmaz. + ## Slash komutları | Komut | Yaptığı | |---|---| | `/reset` | Konuşma geçmişini temizle. | -| `/save ` | Konuşmayı JSONL'a kaydet. | -| `/system ` | System prompt'u ayarla veya güncelle. | -| `/temperature ` | Sampling sıcaklığını ayarla (0.0 - 2.0). | -| `/help` | Bu listeyi göster. | -| `/exit` veya `Ctrl+D` | Çık. | +| `/save [yol]` | Konuşmayı JSONL'a kaydet; yol verilmezse geçerli dizine `chat_history_.jsonl` yazar. | +| `/system [prompt]` | System prompt'u ayarla veya güncelle; argümansız çağrılırsa mevcut olanı gösterir. | +| `/temperature ` | Sampling sıcaklığını ayarla: 0'ın üstünde ve 2.0'a kadar (`0` reddedilir). | +| `/help` veya `/?` | Bu listeyi göster. | +| `/exit`, `/quit` veya `Ctrl+D` | Çık. | ## Konfigürasyon -```yaml -chat: - default_temperature: 0.7 - default_top_p: 0.9 - default_max_tokens: 1024 - default_system_prompt: "Yardımsever bir asistansın." - history_file: "~/.forgelm/chat-history" # oturumlar arası kalıcı -``` +`forgelm chat` YAML config okumaz ve `chat:` bloğu yoktur: komut satırı flag'leri ve yukarıdaki slash komutlarıyla ayarlanır. + +| Ayar | Flag | Varsayılan | +|---|---|---| +| Sohbetten önce birleştirilecek adapter | `--adapter DIR` (`--backend unsloth` ile desteklenmez) | yok | +| Başlangıç system prompt'u | `--system PROMPT` (sonra `/system` ile değiştirin) | yok | +| Sampling temperature | `--temperature` (sonra `/temperature` ile değiştirin) | `0.7` | +| Yanıt başına maksimum token | `--max-new-tokens` | `512` | +| Streaming çıktı | `--no-stream` kapatır | açık | +| Quantize yükleme (yalnızca CUDA + bitsandbytes; aksi halde yok sayılır) | `--load-in-4bit` (NF4) veya `--load-in-8bit`; ikisi de verilirse 4-bit kazanır; `--backend unsloth` ile hiçbiri verilmezse de 4-bit varsayılandır | kapalı | +| Model backend'i | `--backend transformers` veya `--backend unsloth` | `transformers` | +| Modelle birlikte gelen kodu çalıştırma | `--trust-remote-code` | kapalı | + +Top-p, top-k ve repetition penalty sabittir (0.9 / 50 / 1.1) ve ayarlanamaz. Chat her zaman sampling yapar: seed yoktur ve greedy mod yoktur. Oturumlar arası kalıcı bir history dosyası da yoktur; bir konuşmayı `/save` ile kaydedin. Komut satırı flag'i `0.0` sıcaklığı kabul eder, ancak sampling onu reddeder; bu yüzden her tur bir `[Generation error: …]` satırıyla başarısız olur (streaming açıkken, ki varsayılandır, ancak 60 saniyelik bir beklemeden sonra). 0'ın üstünde bir değer kullanın. ## Checkpoint yükleme @@ -53,7 +65,8 @@ chat: - LoRA adapter ağırlıklı dizin: `./checkpoints/run/` - Birleştirilmiş checkpoint dizini: `./checkpoints/run/merged/` - HuggingFace model ID: `Qwen/Qwen2.5-7B-Instruct` -- GGUF dosyası: `./model.gguf` (alta llama.cpp) + +GGUF dosyalarını `forgelm chat` yüklemez: komut yalnızca Hugging Face checkpoint'lerini yükler. Bir GGUF çıktısını denemek için onu Ollama veya llama.cpp'de çalıştırın (bkz. [Deploy Hedefleri](#/deployment/deploy-targets)). LoRA checkpoint'lerinde base modeli pozisyonel argüman olarak verin ve adapter dizinini `--adapter` ile belirtin: @@ -68,7 +81,7 @@ $ forgelm chat "Qwen/Qwen2.5-7B" --adapter ./checkpoints/run/ Planlanan `--safety` bayrağı altında (yalnız önizleme — yukarıdaki nota bakın) her prompt ve yanıt Llama Guard tarafından taranır: ```text -forgelm> [adversarial prompt] +> [adversarial prompt] [Llama Guard S2 (şiddet içermeyen suç) flagledi — reddediliyor] Bunda yardım edemem. Soruyu farklı şekilde sormayı deneyin. ``` @@ -77,29 +90,26 @@ Deployment öncesi modeli jailbreak için sondalarken faydalı. Sıradan sohbett ## Multi-turn yönetimi -Konuşma geçmişi bir oturum içinde korunur. Model tüm chat'i (veya `max_length`'a sığanı) görür: +Konuşma geçmişi bir oturum içinde korunur. Model her prompt'ta system prompt'u ve son 50 soru-cevap çiftini alır; daha eski çiftler bellekte (ve `/save` dosyasında) kalır ama artık gönderilmez. Sınır token'ı değil, çiftleri sayar: ```text -forgelm> Türkiye'nin başkenti neresi? -Ankara. +> Türkiye'nin başkenti neresi? +Assistant: Ankara. -forgelm> nüfusu ne kadar? -[önceki bağlamı kullanır: "nüfusu" Ankara'ya işaret ediyor] -Yaklaşık 5.7 milyon, son tahminlere göre... +> nüfusu ne kadar? +Assistant: Yaklaşık 5.7 milyon, son tahminlere göre... ``` -Sıfırdan başlamak için: `/reset`. +İkinci yanıt, ilk tur onunla birlikte gönderildiği için mümkündür ("nüfusu" Ankara'ya işaret ediyor). Sıfırdan başlamak için: `/reset`. ## Kaydetme ve oynatma ```shell -forgelm> /save sessions/qa-1.jsonl -[6 tur sessions/qa-1.jsonl'a kaydedildi] +> /save sessions/qa-1.jsonl +[History saved → sessions/qa-1.jsonl (7 messages)] ``` -Kaydedilen JSONL, yeni bir oturumun prompt history'sine geri -besleyerek tekrar oynatılabilir (replay tooling gelecekteki bir Pro CLI sürümü için -roadmap'tedir). +Dosya, satır başına bir `{"role": ..., "content": ...}` mesajı içerir: önce (ayarlıysa) system prompt, ardından oturumun tüm kullanıcı ve asistan mesajları. Yazılan system prompt, konuşma sırasında değiştirmiş olsanız bile kaydettiğiniz andaki geçerli olandır. Kaydedilen bir dosyayı bir oturuma geri yükleyen bir komut yoktur (replay tooling gelecekteki bir Pro CLI sürümü için roadmap'tedir). Oturumlar şu durumlar için faydalı: - Test sırasında bulduğunuz bir bug'ı yeniden üretmek. @@ -123,11 +133,11 @@ v1 vs v2 win rate: 0.62 (sig p=0.04) ## Sık hatalar :::warn -**`/temperature 0`'ı determinist saymak.** Yakın-determinist ama argmax sampling'deki eşitlik bozma hâlâ küçük varyans üretebilir. Tam yeniden üretilebilirlik için YAML'da `seed:` ayarlayın. +**Chat'i yeniden üretilebilir saymak.** Chat her zaman sampling yapar; seed'i ve greedy modu yoktur (`/temperature 0` reddedilir), bu yüzden aynı prompt farklı yanıtlar verebilir. Keşif için kullanın; modelin gerçekte ne söylediğini `/save` dökümüyle saklayın ve tekrarlanabilir kontroller için değerlendirme komutlarını kullanın. ::: :::warn -**Context'i aşan uzun konuşmalar.** Geçmiş `max_length`'i aşınca ForgeLM en eski mesajları düşürür. Konuşma süreklilik kaybedebilir. Uzun test oturumlarında periyodik `/reset` kullanın. +**Context'i aşan uzun konuşmalar.** Yalnızca son 50 çift gönderilir, ancak hiçbir şey token saymaz: uzun bir system prompt, uzun yanıtlar ya da küçük context'li bir model yine de taşabilir. Modele bağlı olarak tur ya bir `[Generation error: …]` satırıyla başarısız olur (geçmişe eklenmez) ya da üretim bozulmuş çıktıyla devam eder ve yalnızca `transformers` bir uyarı verir. Uzun test oturumlarında periyodik `/reset` kullanın. ::: :::tip diff --git a/docs/usermanuals/tr/evaluation/safety.md b/docs/usermanuals/tr/evaluation/safety.md index 83ae7477..f8cdc49a 100644 --- a/docs/usermanuals/tr/evaluation/safety.md +++ b/docs/usermanuals/tr/evaluation/safety.md @@ -187,7 +187,7 @@ Set bir *seed*'tir — safety skorunu release-gate olarak kullanmadan önce kend Bu örnek varsayılan `classifier_mode: generation` altında üretilmiştir (yukarıdaki uyarıya bakın): `safety_score`, `safe_ratio`'ya tam olarak eşittir çünkü `confidence_weighted` bu modda bir safe-ratio ortalamasına dejenere olur; `details[].confidence` da well-formed bir `unsafe` verdict için `1.0`'dır — gerçek bir probability değil. `failure_reason`, `forgelm/safety/_gates.py::_evaluate_safety_gates`'teki her zaman aktif mutlak kapıdan gelir: `total_count=50`'nin `unsafe_count=3`'ü %6.00'lık bir unsafe oranıdır, ki bu varsayılan `max_safety_regression=0.05` (%5.00) tavanını aşar — bu kapı `scoring_method`'dan bağımsız olarak ateşlenir. `severity_distribution`, `track_categories: true` iken her zaman dört severity seviyesinin tümünü (`critical`/`high`/`medium`/`low`) sıfır-doldurulmuş olarak listeler; burada unsafe, well-formed, kategori-etiketli iki yanıt da `S5` (iftira) idi, ki bu `forgelm/safety/_types.py`'nin `CATEGORY_SEVERITY`'sinde `high` değil `medium`'a eşlenir. Üçüncü unsafe yanıt (`low_confidence_count`'ta sayılan), malformed bir guard verdict'idir — fail-closed skorlanır ve kategori/severity dökümünden hariç tutulur. `unscored_count: 1` tam olarak bunu kaydeder; `scored_unsafe_count`'ın `2` olmasının nedeni de budur: bu iki alan `unsafe_count`'u, sınıflandırıcının gerçekten unsafe olarak *okuduğu* çiftler ile hiç cevap veremediği çiftler olarak ikiye ayırır. Bir başarısızlığa göre aksiyon almadan önce bunları okuyun — unsafe oranı alıntılayan bir `failure_reason` aynı ayrıştırmayı her zaman düz metin olarak da ekler, çünkü aksi hâlde altı malformed verdict ile gerçekten zararlı altı completion birebir aynı cümleyi üretir. `evaluation_completed`, auto-revert'in baktığı alandır: `false`, koşumun model hakkında kullanılabilir bir kanıt olmadığı anlamına gelir (sınıflandırıcı kullanılamazdı ya da başarısızlık tamamen unscored çiftlere atfedilebilirdi); bu durumda model **başarısız sayılır ama silinmez** ve `forgelm safety-eval` `3` yerine `2` ile çıkar. -`category_distribution` / `severity_distribution` yalnızca `track_categories: true` iken mevcuttur. `details[].prompt`, `details[].response` ve `details[].raw_verdict` GDPR / EU AI Act Madde 10 gizliliği için varsayılan olarak temizlenir — debug için ham metni saklamak üzere `include_eval_samples: true` ayarlayın. Üçüncü alanın ne olduğuna dikkat edin: varsayılan `classifier_mode: generation` altında `raw_verdict`, guard'ın kendi ürettiği çıktıdır ve 200 karaktere kırpılır. Bir koşum "değerlendirme yapılamadı" raporladığında okunacak alan odur — ancak yanlış yapılandırılmış bir guard probe'a cevap vermek yerine onu yankılar veya sürdürür; dolayısıyla bu anahtarı açmak probe metnini diske ikinci bir yoldan yazabilir. `details[].label` her iki durumda da artefaktta kalır; model çıktısından kesilerek değil sabit bir sözlükten yeniden kurulur. Bu sözlük kapalı ve boşlukla ayrılmıştır: `safe`, `malformed` ya da isteğe bağlı olarak virgülle birleştirilmiş S-kodlarının izlediği `unsafe` — `"unsafe"`, `"unsafe S5"`, `"unsafe S1,S5"`. Yeni satıra göre değil, boşluğa göre parse edin: bu sayfanın önceki sürümleri, etiketin ham model çıktısından kesildiği dönemdeki `"unsafe\nS5"` biçimini gösteriyordu. `\n` üzerinden bölen bir tüketici, güncel hiçbir artefakttan S-kodu çıkaramaz. +`category_distribution` / `severity_distribution` yalnızca `track_categories: true` iken mevcuttur. `details[].prompt`, `details[].response` ve `details[].raw_verdict` GDPR Madde 25(2)'deki varsayılan veri koruma yükümlülüğü gereği varsayılan olarak temizlenir — debug için ham metni saklamak üzere `include_eval_samples: true` ayarlayın. Üçüncü alanın ne olduğuna dikkat edin: varsayılan `classifier_mode: generation` altında `raw_verdict`, guard'ın kendi ürettiği çıktıdır ve 200 karaktere kırpılır. Bir koşum "değerlendirme yapılamadı" raporladığında okunacak alan odur — ancak yanlış yapılandırılmış bir guard probe'a cevap vermek yerine onu yankılar veya sürdürür; dolayısıyla bu anahtarı açmak probe metnini diske ikinci bir yoldan yazabilir. `details[].label` her iki durumda da artefaktta kalır; model çıktısından kesilerek değil sabit bir sözlükten yeniden kurulur. Bu sözlük kapalı ve boşlukla ayrılmıştır: `safe`, `malformed` ya da isteğe bağlı olarak virgülle birleştirilmiş S-kodlarının izlediği `unsafe` — `"unsafe"`, `"unsafe S5"`, `"unsafe S1,S5"`. Yeni satıra göre değil, boşluğa göre parse edin: bu sayfanın önceki sürümleri, etiketin ham model çıktısından kesildiği dönemdeki `"unsafe\nS5"` biçimini gösteriyordu. `\n` üzerinden bölen bir tüketici, güncel hiçbir artefakttan S-kodu çıkaramaz. `safety_trend.jsonl` koşu başına bir JSON objesi ekler: @@ -211,7 +211,7 @@ Bu örnek varsayılan `classifier_mode: generation` altında üretilmiştir (yuk | `track_categories` | bool | `false` | Yanıt başı Llama Guard S1-S14 kategorilerini parse et ve raporda yüzeye çıkar. | | `severity_thresholds` | `Optional[Dict[str,float]]` | `null` | Severity-başı unsafe-ratio tavanları — yukarıdaki Severity eşikleri'ne bakın. | | `batch_size` | int | `8` | Fine-tuned modelin probe yanıtları için batched generation boyutu; `1` batching'i kapatır. Guard-verdict skorlamasına **uygulanmaz** — o her zaman sıralıdır, bkz. aşağıdaki Sık hatalar. | -| `include_eval_samples` | bool | `false` | Ham `prompt` / `response` / `raw_verdict` string'lerini `safety_results.json`'a kaydeder. GDPR / EU AI Act Madde 10 gizliliği için varsayılan kapalı — `raw_verdict`, `classifier_mode: generation` altında guard'ın kendi ürettiği metindir. | +| `include_eval_samples` | bool | `false` | Ham `prompt` / `response` / `raw_verdict` string'lerini `safety_results.json`'a kaydeder. GDPR Madde 25(2)'deki varsayılan veri koruma yükümlülüğü gereği varsayılan kapalı — `raw_verdict`, `classifier_mode: generation` altında guard'ın kendi ürettiği metindir. | ## Sık hatalar diff --git a/docs/usermanuals/tr/operations/gpu-cost.md b/docs/usermanuals/tr/operations/gpu-cost.md index 80f07a57..b85c2aad 100644 --- a/docs/usermanuals/tr/operations/gpu-cost.md +++ b/docs/usermanuals/tr/operations/gpu-cost.md @@ -1,155 +1,157 @@ --- title: GPU Maliyet Tahmini -description: 16 GPU profilinde otomatik tespit ve saatlik tarifenize göre koşu başı maliyet izleme. +description: GPU'yu tespit eder, koşuyu sizin saatlik tarifenizle ya da yerleşik bir tabloyla fiyatlandırır ve her koşunun maliyetini kaydeder. --- # GPU Maliyet Tahmini -> **Durum:** GPU tespiti + koşu süresi + audit-log damgalama bugün -> gönderiliyor; konfigürasyon-tabanlı `cost_tracking:` bloğu (tarife +> **Durum:** GPU tespiti, eğitim süresi ve maliyet tahmini bugün her koşu için +> değerlendirme-kaybı ve benchmark kapılarını geçen her koşu için kaydedilir ve `training.gpu_cost_per_hour` tek maliyet ayarıdır. +> Konfigürasyon-tabanlı bir `cost_tracking:` bloğu (GPU başına tarife > tabloları, alarm / durdurma eşikleri) **yol haritasında ve henüz > uygulanmadı** — `forgelm/config.py`'de `output.cost_tracking` yüzeyi -> yok. Aşağıdaki `cost_tracking:` örnekleri ileriye dönük yer -> tutuculardır — YAML yüzeyi inene kadar saatlik tarifeleri manuel -> ayarlayın. Erteleme için bkz. [GitHub'daki risks-and-decisions yol haritası](https://github.com/HodeTech/ForgeLM/blob/main/docs/roadmap/risks-and-decisions.md). +> yok, bu yüzden bu sayfadaki hiçbir örnek onu kullanmaz. Erteleme için bkz. +> [GitHub'daki risks-and-decisions yol haritası](https://github.com/HodeTech/ForgeLM/blob/main/docs/roadmap/risks-and-decisions.md). -ForgeLM, üzerinde koştuğunuz GPU'yu tespit eder, profilini (bellek, compute, tipik saatlik tarife) bulur ve koşu başı maliyeti izler. Her koşunun ardından audit log tam olarak ne kadar GPU zamanı kullanıldığını ve maliyetini kaydeder. +Bir eğitim koşusu değerlendirme-kaybı ve benchmark kapılarını geçtiğinde (birinde başarısız olan koşu maliyet toplanmadan döner) ForgeLM, ne kadar süre eğitildiğini, kaç GPU üzerinde koştuğunu ve bunun saatlik bir tarifeyle ne tuttuğunu kaydeder: tarifeyi siz verdiyseniz sizinki, vermediyseniz tespit edilen GPU için yerleşik bir fiyat kullanılır. Tahmin diğer koşu sonuçlarının yanına yazılır; fatura değildir ve hiçbir şeyi durdurmaz ya da uyarmaz. ## Tespit nasıl çalışır -Başlangıçta `forgelm` şunları okur: -- Donanım tanımlama için `nvidia-smi --query-gpu=name,memory.total,...`. -- `forgelm/trainer.py` içindeki yerleşik GPU fiyat tablosundan eşleşen saatlik ücret. +Eğitimden sonra ForgeLM, PyTorch'tan ilk CUDA aygıtının adını (`torch.cuda.get_device_name(0)`), sürecin görebildiği CUDA aygıtı sayısını ve ilk aygıtta ayrılan tepe belleği ister. Eğitim süresini Hugging Face trainer'ının `train_runtime` değerinden alır. `nvidia-smi` çağırmaz. CUDA GPU yoksa GPU alanlarının hiçbiri kaydedilmez. -Desteklenen GPU'lar: +Tarife, ayarlıysa `training.gpu_cost_per_hour`'dan; değilse `forgelm/trainer.py` içindeki `_GPU_PRICING` fiyat tablosundan gelir ve PyTorch'un bildirdiği tam aygıt adıyla eşleştirilir: -| Sınıf | Modeller | +| Sınıf | Tanınan adlar | |---|---| -| Datacenter | A100 40 GB / 80 GB, H100 80 GB, H200, L40S | -| Workstation | RTX 6000 Ada, RTX A6000 | -| Consumer | RTX 4090, RTX 4080, RTX 3090, RTX 3080 | -| Sadece-cloud | T4, V100, A10G | -| Apple | M1/M2/M3 Max (CPU/MPS fallback) | +| Tesla | Tesla T4, Tesla P100, Tesla V100, Tesla K80 | +| NVIDIA data center | NVIDIA A10G, NVIDIA A100-SXM4-40GB, NVIDIA A100-SXM4-80GB, NVIDIA A100 80GB PCIe, NVIDIA H100 80GB HBM3, NVIDIA H100 SXM5 80GB, NVIDIA H200, NVIDIA L4, NVIDIA L40S, NVIDIA B200 | +| GeForce (kendi sunucunuz) | NVIDIA GeForce RTX 3090, NVIDIA GeForce RTX 4090 | -GPU'nuz tanınmazsa ForgeLM uyarı bırakır ve generic profile fallback yapar (maliyet tahmini yok ama eğitim çalışır). +Tablodaki fiyatlar yaklaşık on-demand cloud ortalamalarıdır (GeForce kartlarında elektrik artı amortisman tahmini). Tabloda olmayan bir GPU için, örneğin `NVIDIA RTX A6000` ya da `NVIDIA H100 PCIe`, **uyarı verilmez**: `training.gpu_cost_per_hour` ayarlamadıysanız koşunun tahmini bir maliyeti olmaz. ## Saatlik tarife konfigürasyonu ```yaml -output: - cost_tracking: - enabled: true - rate_per_hour: - A100_80GB: 1.10 # saatlik USD - A100_40GB: 0.85 - H100_80GB: 2.40 - RTX_4090: 0.50 # kabaca elektrik maliyeti - default: 1.00 # eşleşmeyen GPU'lar için - currency: "USD" +training: + gpu_cost_per_hour: 1.10 # GPU başına saatlik USD ``` -`cost_tracking:` yüzeyi indiğinde, bunu proje başına bir kez ayarlayın ve ForgeLM maliyet raporlama için eşleşen tarifeyi kullanacak. O zamana kadar blok ileriye dönük bir yer tutucudur (yukarıdaki durum bandına bakın). +`training.gpu_cost_per_hour`'u gerçek tarifenize ayarlayın. Ayarlı değilse ForgeLM tespit edilen GPU için yerleşik fiyat tablosuna döner; koşunun `resource_usage` kaydı hangisinin kullanıldığını `cost_source` alanında yazar: + +| `cost_source` | Anlamı | +|---|---| +| `user_config` | Tarife `training.gpu_cost_per_hour`'dan geldi (`0` kabul edilir ve sıfır maliyet kaydeder). | +| `auto_detected` | Aygıt adı bir tablo girdisiyle tam eşleşti. | +| `fuzzy_match` | Aygıt adı bir tablo girdisiyle kısmen eşleşti. Bu bir tahmindir: örneğin `NVIDIA L40`, `NVIDIA L40S` fiyatıyla hesaplanır. | + +Hiçbir tarife belirlenemediğinde `cost_source`, `gpu_cost_per_hour_usd` ve `estimated_cost_usd` bulunmaz. + +:::warn +**CUDA GPU'su olmayan makineler (CPU, Apple silicon).** Aygıt adı yoktur ve boş bir ad tablonun her girdisiyle kısmen eşleşir ve en uzun ad, `NVIDIA GeForce RTX 3090`, seçilir; bu yüzden koşu, `cost_source: fuzzy_match` ile yer tutucu bir `0.15` USD/saat tarifesiyle kaydedilir. Bunu gerçek bir maliyet saymayın; böyle makinelerde `training.gpu_cost_per_hour`'u açıkça ayarlayın (koşu size ücretsizse `0`). +::: + +Koşu başına tek bir tarife vardır ve bu tarife GPU başınadır: GPU modeli başına tarife tabloları desteklenmez. ## Çıktı -Her koşunun ardından audit log şunları kaydeder: +Tahmin şu yerlerde görünür: + +- **`compliance/compliance_report.json`**, `resource_usage` altında; koşunun `training.output_dir` dizininde, güvenlik ve judge kapılarını da geçen bir koşunun sonunda yazılır (geri alınan koşu rapor dışa aktarmaz). Aynı veri `annex_iv_metadata.json` içine `computational_resources` olarak kopyalanır. +- **`--output-format json` sonucu**: `resource_usage` ve üst düzey `estimated_cost_usd` olarak. +- **Konsol**: `Estimated training cost` satırı ve fiyat kaynağını belirten bir `GPU-hours` satırı (text modunda). +- **Koşu metrikleri**: her sayısal alan `resource/` olarak kopyalanır (örneğin `resource/estimated_cost_usd`). Kopyalar sonucun `metrics` alanında, başarı webhook'unda ve `pipeline.completed` audit olayının `metrics_summary` alanında görünür (koşu insan-onayı kapısında durduğunda olay yayımlanmaz). W&B, MLflow ya da TensorBoard'a gönderilmezler. + +`gpu_cost_per_hour: 1.10` ile 1892 saniye eğitilen 4 GPU'lu bir koşu şunu kaydeder: ```json { - "event": "run_complete", - "ts": "2026-04-29T14:33:10Z", - "duration_seconds": 1892, - "gpu_profile": "A100_80GB", - "gpus_used": 1, - "estimated_cost_usd": 0.578, - "rate_per_hour": 1.10, - "currency": "USD" + "resource_usage": { + "gpu_model": "NVIDIA A100-SXM4-80GB", + "peak_vram_gb": 41.7, + "gpu_count": 4, + "training_duration_seconds": 1892.0, + "gpu_hours": 2.102, + "cost_source": "user_config", + "gpu_cost_per_hour_usd": 1.1, + "estimated_cost_usd": 2.3124 + } } ``` -Model card atıf yapar: - -```markdown -## Eğitim maliyeti - -Bu model 1× A100 80GB üzerinde 31m 32s eğitildi, -konfigüre tarifeyle tahmini $0.58 USD. -``` +Model card maliyeti içermez. ## Uçuş öncesi maliyet tahmini -Uzun bir eğitim koşusuna başlamadan önce kısa bir kalibrasyon (1-2 adım, `training.max_steps: 2` ile) koşturun, koşum-başına `compliance_report.json`'dan ortaya çıkan `gpu_hours`'u yakalayın ve sağlayıcınızın saatlik tarifesiyle çarpın. (Özel bir `--estimate-cost` flag'i tartışıldı ama ship edilmedi; resource-tracking yolu yalnızca gerçek değerleri yayar.) +Uzun bir eğitim koşusuna başlamadan önce sert bir adım sınırıyla kısa bir kalibrasyon koşturun, `resource_usage` içindeki `gpu_hours`'u okuyun, tam adım sayısına ölçekleyin ve saatlik tarifenizle çarpın. Özel bir `--estimate-cost` flag'i tartışıldı ama ship edilmedi; resource-tracking yolu yalnızca gerçek değerleri kaydeder. + +```yaml +# configs/calibration.yaml — gerçek config'iniz ve ek olarak: +training: + max_steps: 20 + output_dir: /tmp/calib +``` ```shell -$ forgelm --config configs/calibration.yaml --output-dir /tmp/calib -$ jq '.resource_usage.gpu_hours' /tmp/calib/compliance_report.json +$ forgelm --config configs/calibration.yaml +$ jq '.resource_usage.gpu_hours' /tmp/calib/compliance/compliance_report.json 0.034 -$ python -c "print(0.034 * (3 / 0.034) * 1.10)" # 6h eğitim, $1.10/sa -$7.15 +$ python -c "print(round(0.034 * (3000 / 20) * 1.10, 2))" # 3.000 adım, GPU-saat başına $1.10 +5.61 ``` -Kalibrasyon yaklaşımı genelde gerçeğin %20 içindedir. +Sonucu kaba bir rakam olarak değerlendirin. İlk adımlar ısınma içerir ve birkaç adımdaki dizi uzunluğu karışımı nadiren tüm veri setininkiyle örtüşür; ölçeklemeden önce kararlı bir adım süresi görecek kadar adım koşturun. ## Çoklu-GPU ve dağıtık -Çoklu-GPU eğitiminde ForgeLM GPU başı tarifeyi GPU sayısıyla çarpar: - -```yaml -output: - cost_tracking: - rate_per_hour: - A100_80GB: 1.10 -``` +`gpu_hours`, eğitim süresinin sürecin görebildiği CUDA aygıtı sayısıyla çarpımıdır ve tek `training.gpu_cost_per_hour` tarifesi GPU başına uygulanır. 4×A100 koşusu 2 saat = 4 × 2 × $1.10 = $8.80; ZeRO veya FSDP kullanmak fark etmez. +İş, makinedeki GPU'ların bir kısmını kullanıyorsa sürecin görebileceği aygıtları sınırlayın (örneğin `CUDA_VISIBLE_DEVICES` ile); aksi halde tahmin, koşunun hiç kullanmadığı GPU'ları da sayar. + ## Maliyet alarmları (yol haritası — henüz uygulanmadı) Kontrolden çıkabilecek koşular için planlanan `cost_tracking` bloğu eşik -tabanlı alarm + durdurma destekleyecek: +tabanlı alarmı (alarm eşiği aşılınca webhook tetiklenir) ve durdurmayı +(durdurma eşiğinde eğitim durur) destekleyecek. Böyle bir ayar bugün +`forgelm/config.py`'de yoktur. -```yaml -# yol haritası yer tutucusu — forgelm/config.py tarafından henüz honure edilmez -output: - cost_tracking: - alert_threshold_usd: 50.0 # geçildiğinde webhook fırlat - halt_threshold_usd: 200.0 # eğitim durur -``` - -İmplemente edildiğinde, alarm konfigüre webhook'u fırlatır (bkz. [Webhook'lar](#/operations/webhooks)) — yanlış konfigüre koşunun gece bütçeyi haftalık bitirmesini CI'da yakalamak için faydalı. O zamana kadar maliyeti audit log + zamanlayıcınızdaki bütçe-tarafı koruyucu ile manuel izleyin. +İmplemente edildiğinde, alarm konfigüre webhook'u fırlatır (bkz. [Webhook'lar](#/operations/webhooks)) — yanlış konfigüre koşunun gece bütçeyi haftalık bitirmesini CI'da yakalamak için faydalı. O zamana kadar maliyeti kaydedilen `resource_usage` ve zamanlayıcınızdaki bütçe-tarafı koruyucu ile manuel izleyin. ## Özel GPU profilleri -Varsayılan profilde olmayan GPU eklemek için: +:::warn +**Uygulanmadı.** ForgeLM'de `gpu_profiles` ayarı yoktur ve `gpu_profiles.local.yaml` okunmaz; aşağıdaki snippet yalnızca planlanan biçimdir ve onu içeren bir config reddedilir. Bugün tabloda olmayan bir GPU'yu fiyatlandırmanın yolu, her GPU için çalışan `training.gpu_cost_per_hour`'dur. +::: + +Yerleşik tabloda olmayan bir GPU eklemek için (planlanan): ```yaml gpu_profiles: custom: - name: "AcmeNVIDIA-XYZ" - pattern: "AcmeNVIDIA-XYZ" # nvidia-smi name'ye karşı eşleştir + pattern: "AcmeNVIDIA-XYZ" # aygıt adına karşı eşleştir memory_gb: 96 compute_capability: 9.0 tensor_cores: true ``` -Bunu proje kökünüze `gpu_profiles.local.yaml` olarak bırakın; ForgeLM otomatik birleştirir. - ## Sık hatalar :::warn -**Saatlik tarifeyi mutlak doğru saymak.** ForgeLM'in varsayılanları makul ortalamalar — gerçek cloud faturanız instance tipi, bölge, spot vs on-demand ve indirimlerinize bağlı. Kendi gerçek tarifenizle override edin. +**Saatlik tarifeyi mutlak doğru saymak.** ForgeLM'in varsayılanları kaba ortalamalar — gerçek cloud faturanız instance tipi, bölge, spot vs on-demand ve indirimlerinize bağlı. Kendi gerçek tarifenizle override edin. ::: :::warn -**Paylaşımlı GPU'larda multi-tenancy.** Birden çok iş bir GPU'yu paylaşıyorsa (eğitimde nadir, inference'ta yaygın), maliyet izleme bölmek yerine çarpıyor. Gerçek tahsisi belirtmek için `--gpus N` kullanın. +**Paylaşımlı GPU'lar.** Tahmin, görünen her GPU'daki eğitim süresinin tamamını faturalar; kesirli ya da paylaşımlı tahsis için bir ayar yoktur. GPU'yu başka işlerle paylaşıyorsanız rakamı kendiniz bölün. ::: :::tip -**Maliyeti zaman içinde izleyin.** Audit log her koşunun maliyetini içerir; haftalar boyunca trend alın, sürünen aşımları yakalayın. Aynı eğitim süresinde 2× maliyet artışı genelde verinin büyüdüğü veya hyperparam'ların kaydığı işaretidir. +**Maliyeti zaman içinde izleyin.** Her koşu maliyetini kaydeder; haftalar boyunca trend alın, sürünen aşımları yakalayın. Aynı eğitim süresinde 2× maliyet artışı genelde verinin büyüdüğü veya hyperparam'ların kaydığı işaretidir. ::: ## Bkz. - [VRAM Fit-Check](#/operations/vram-fit-check) — maliyet tahmini ile birlikte koşar. - [CI/CD Hatları](#/operations/cicd) — CI'da maliyet alarmları. -- [Konfigürasyon Referansı](#/reference/configuration) — tam `cost_tracking` bloğu. +- [Konfigürasyon Referansı](#/reference/configuration) — `training.gpu_cost_per_hour` alanı. diff --git a/docs/usermanuals/tr/reference/cli.md b/docs/usermanuals/tr/reference/cli.md index c552e54b..c116cc18 100644 --- a/docs/usermanuals/tr/reference/cli.md +++ b/docs/usermanuals/tr/reference/cli.md @@ -26,8 +26,8 @@ ForgeLM, subcommand'larla tek bir `forgelm` binary'si yayınlar. Bu sayfa kanoni | `forgelm approve` | İnsan onay isteğini imzala ve `final_model.staging/`'i promote et. | | `forgelm reject` | İnsan onay isteğini reddet; staging dizini adli inceleme için korunur. | | `forgelm approvals` | Bekleyen onayları listele (`--pending`) veya tek birini incele (`--show RUN_ID`). | -| `forgelm purge` | GDPR Madde 17 silme: row-id, run-id veya `--check-policy` retention raporu. | -| `forgelm reverse-pii` | GDPR Madde 15 erişim hakkı: maskelenmiş corpora'da subject kimlik bilgisini ara (plaintext veya hash-mask scan). | +| `forgelm purge` | GDPR Madde 17 silmenin bir adımı: row-id, run-id veya `--check-policy` retention raporu. | +| `forgelm reverse-pii` | GDPR Madde 15 yanıtının bir girdisi: corpus'larda veri sahibinin identifier'ını ara (plaintext veya hash-mask scan). | | `forgelm cache-models` | Air-gap workflow: bir veya birden fazla model için HuggingFace Hub cache'ini önceden doldur. | | `forgelm cache-tasks` | Air-gap workflow: lm-eval task dataset cache'ini önceden doldur (`[eval]` extra'sı gerekir). | | `forgelm safety-eval` | Bir model checkpoint'ine karşı standalone safety evaluation (varsayılan Llama Guard). | diff --git a/docs/usermanuals/tr/reference/configuration.md b/docs/usermanuals/tr/reference/configuration.md index 753f164c..8f79faa2 100644 --- a/docs/usermanuals/tr/reference/configuration.md +++ b/docs/usermanuals/tr/reference/configuration.md @@ -367,11 +367,11 @@ kaynağı kaydedilmemiştir. ```yaml retention: - audit_log_retention_days: 1825 # varsayılan 5 yıl (Madde 5(1)(e)) - staging_ttl_days: 7 # forgelm reject sonrası staging modelini saklama süresi + audit_log_retention_days: 1825 # 5 yıl: ForgeLM varsayılanı (proje tercihi; GDPR Madde 5(1)(e) sabit süre koymaz) + staging_ttl_days: 7 # staging dizininin purge --check-policy tarafından süresi geçmiş sayıldığı yaş ephemeral_artefact_retention_days: 90 # uyumluluk paketleri, denetim raporları raw_documents_retention_days: 90 # ingest edilen PDF/DOCX/EPUB/TXT/Markdown - enforce: "log_only" # ayrılmış: henüz hiçbir komut onu kullanmaz (log_only | warn_on_excess | block_on_excess) + enforce: "log_only" # ayrılmış: henüz hiçbir komut onu dikkate almaz (log_only | warn_on_excess | block_on_excess) ``` ## `pipeline:` diff --git a/docs/usermanuals/tr/training/lora.md b/docs/usermanuals/tr/training/lora.md index ee466c1d..27f5b24f 100644 --- a/docs/usermanuals/tr/training/lora.md +++ b/docs/usermanuals/tr/training/lora.md @@ -92,14 +92,13 @@ Rank LoRA kalitesi için en önemli hyperparam. ```yaml lora: - target_modules: "all-linear" # en geniş — her Linear - # veya: - target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] - # veya sadece attention: + # daha geniş — attention ve MLP projeksiyonları (bir liste; "all-linear" string'i kabul edilmez): + # target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] + # veya yalnızca query ve value projeksiyonları (varsayılan): target_modules: ["q_proj", "v_proj"] ``` -Geniş = daha çok kapasite ama daha çok VRAM. Varsayılan (Q/K/V/O) çoğu görev için doğru denge. +Geniş = daha çok kapasite ama daha çok VRAM. Varsayılan (`["q_proj", "v_proj"]`, query ve value projeksiyonları) çoğu görev için doğru denge. ## DoRA — ne zaman değer diff --git a/forgelm/cli/_config_load.py b/forgelm/cli/_config_load.py index 04c846e7..0338fb8e 100644 --- a/forgelm/cli/_config_load.py +++ b/forgelm/cli/_config_load.py @@ -15,7 +15,7 @@ def _warn_reserved_retention_enforce(config) -> None: - """``retention.enforce`` is validated and offered by both wizards, but no command reads it. + """``retention.enforce`` is validated and offered by both wizards, but no command acts on it. A config that sets ``warn_on_excess`` or ``block_on_excess`` is asking for a gate that does not exist; say so, rather than accept it silently. (This lives here, not in the schema, to keep ``config.py`` within its diff --git a/forgelm/cli/_parser.py b/forgelm/cli/_parser.py index 4bd75f23..a8682139 100644 --- a/forgelm/cli/_parser.py +++ b/forgelm/cli/_parser.py @@ -628,8 +628,8 @@ def _add_audit_subcommand(subparsers) -> None: "Phase 12.5: language code passed to Presidio's NLP engine when --pii-ml is on " "(default: 'en'). Presidio raises a typed exception if no engine is registered for " "the requested language — surface it to the operator instead of silently " - "running an English NER on a non-English corpus. Set to e.g. 'tr' on a Turkish " - "corpus AND make sure the matching spaCy model is installed." + "running an English NER on a non-English corpus. Set to e.g. 'de' on a German corpus, or 'xx' (multilingual fallback) for a language " + "with no dedicated spaCy model such as Turkish; the matching spaCy model must be installed." ), ) p.add_argument( @@ -1077,7 +1077,7 @@ def _add_purge_subcommand(subparsers) -> None: - ``--row-id --corpus `` — atomic JSONL row erasure with hashed audit event (Article 17 right-to-erasure for training rows). - ``--run-id --kind {staging,artefacts}`` — run-scoped artefact - erasure (staging directory or compliance bundle). + erasure (staging directory or compliance files whose names embed the run id). - ``--check-policy`` — read-only retention-policy violation report against the loaded config's ``retention:`` block. @@ -1089,10 +1089,10 @@ def _add_purge_subcommand(subparsers) -> None: """ p = subparsers.add_parser( "purge", - help="GDPR Article 17 erasure: remove corpus rows or run-scoped artefacts.", + help="Remove corpus rows or run-scoped artefacts (one step of a GDPR Article 17 erasure).", description=( - "Article 17 right-to-erasure: deletes a corpus row, a staging " - "directory, or a run's compliance bundle, and records a tamper-evident " + "One step of a GDPR Article 17 erasure: deletes a corpus row, a staging " + "directory, or a run's compliance files whose names embed the run id, and records a tamper-evident " "audit-event chain (request → completed / failed). Use --check-policy " "for a read-only retention-violation report against the loaded config." ), @@ -1115,7 +1115,7 @@ def _add_purge_subcommand(subparsers) -> None: mode.add_argument( "--check-policy", action="store_true", - help="Read-only retention-policy violation report. Always exits 0 (report-not-gate).", + help="Read-only retention-policy violation report. A successful report exits 0 (report-not-gate); a --config that cannot be loaded exits 1.", ) p.add_argument( "--corpus", @@ -1129,7 +1129,7 @@ def _add_purge_subcommand(subparsers) -> None: type=str, default=None, choices=["staging", "artefacts"], - help="Run-scoped erasure target: staging directory OR compliance bundle. `logs` is intentionally absent (audit logs are append-only Article 17(3)(b)).", + help="Run-scoped erasure target: staging directory OR compliance files whose names embed the run id (the standard compliance bundle has fixed filenames without a run id: a real run id never matches it, a generic token such as `json` does). `logs` is intentionally absent (the audit log is append-only and never deleted by this command).", ) p.add_argument( "--row-matches", @@ -1143,14 +1143,14 @@ def _add_purge_subcommand(subparsers) -> None: type=str, default=None, metavar="DIR", - help="Output directory containing audit_log.jsonl + the per-output-dir salt file. Defaults to corpus parent dir in row mode; required in run mode.", + help="Output directory containing audit_log.jsonl + the per-output-dir salt file. Defaults to the corpus parent dir in row mode and to the current directory in run mode and with --check-policy (where it names the directory to scan).", ) p.add_argument( "--config", type=str, default=argparse.SUPPRESS, metavar="PATH", - help="YAML config (optional in row/run mode; required for --check-policy). Detects the webhook block + retention horizons.", + help="YAML config. Row mode: detects the webhook block for the external-copies warning. --check-policy: supplies the `retention:` horizons (without it nothing is checked and the report says so). Run mode does not read it.", ) p.add_argument( "--justification", @@ -1163,7 +1163,7 @@ def _add_purge_subcommand(subparsers) -> None: "--dry-run", action="store_true", default=argparse.SUPPRESS, - help="Show what would be deleted; do not modify disk. Audit events still emit (with dry_run=True) so the chain reflects intent.", + help="Show what would be deleted without touching the corpus or the staging files. In row mode the salt file is still created; audit events still emit (with dry_run=True) in both modes so the chain reflects intent.", ) _add_common_subparser_flags(p, include_output_format=True) @@ -1190,13 +1190,13 @@ def _add_reverse_pii_subcommand(subparsers) -> None: """ p = subparsers.add_parser( "reverse-pii", - help="GDPR Article 15 right-of-access: search masked corpora for a subject's identifier.", + help="Search corpora for a subject's identifier (an input to a GDPR Article 15 response).", description=( - "Article 15 right-of-access: walks a glob of JSONL corpora and reports " + "An input to a GDPR Article 15 response: walks a glob of JSONL corpora and reports " "every line where the supplied identifier appears (plaintext residual " "scan by default; hash-mask scan when --salt-source is set). Audit " "event `data.access_request_query` records the hashed identifier so " - "the chain itself does not leak the subject's data." + "the chain does not store it in clear (a salted digest is personal data, not anonymous data)." ), ) p.add_argument( @@ -1294,8 +1294,8 @@ def parse_args() -> argparse.Namespace: " forgelm approve RUN_ID Promote a staged model after human review (Art. 14)\n" " forgelm reject RUN_ID Reject a staged model (preserves staging dir for forensics)\n" " forgelm approvals --pending List runs awaiting human approval (or --show RUN_ID)\n" - " forgelm purge --row-id ID GDPR Article 17 erasure (corpus row / run artefacts / --check-policy)\n" - " forgelm reverse-pii --query VAL GDPR Article 15 right-of-access (find subject identifier in masked corpora)\n" + " forgelm purge --row-id ID One step of a GDPR Art. 17 erasure (corpus row / run artefacts / --check-policy)\n" + " forgelm reverse-pii --query VAL Input to a GDPR Art. 15 response (find a subject identifier in corpora)\n" " forgelm cache-models --model M Pre-populate HF Hub cache (air-gap workflow)\n" " forgelm cache-tasks --tasks CSV Pre-populate lm-eval task datasets (requires [eval] extra)\n" " forgelm verify-annex-iv PATH Verify EU AI Act Annex IV artifact (field set + manifest hash)\n" diff --git a/forgelm/cli/subcommands/_purge.py b/forgelm/cli/subcommands/_purge.py index 97f139aa..22ceb3f7 100644 --- a/forgelm/cli/subcommands/_purge.py +++ b/forgelm/cli/subcommands/_purge.py @@ -7,7 +7,7 @@ erasure with hashed audit event (Article 17 right-to-erasure for training corpus rows). - ``forgelm purge --run-id --kind {staging,artefacts}`` — - run-scoped artefact erasure (staging directory or compliance bundle). + run-scoped artefact erasure (staging directory or compliance files whose names embed the run id). - ``forgelm purge --check-policy [--config ]`` — read-only retention-policy violation report against the loaded config's ``retention:`` block. @@ -28,9 +28,9 @@ the chain shows ``request → fail`` instead of ``request → complete``, so an operator scanning the log can spot the unfinished erasure. - ``data.erasure_warning_memorisation`` — corpus row erasure ran - while a ``final_model/`` exists for any run that consumed this - corpus. The row is gone from disk but may still be memorised in - the trained weights. + while a ``final_model/`` directory exists directly under the output + dir (which runs consumed the corpus is not determined). The row is + gone from disk but may still be memorised in the trained weights. - ``data.erasure_warning_synthetic_data_present`` — corpus row erasure ran with synthetic-data snapshots present; the row may have produced derivative snippets the row→snippet mapping no longer connects. @@ -40,10 +40,10 @@ Exit-code contract (per ``docs/standards/error-handling.md``): -- 0 — success or ``--check-policy`` report (gate-not-report semantic - per design §10 Q5). -- 1 — config error (unknown ``row-id``, missing corpus, mutually - exclusive flag combination, conflicting retention values). +- 0 — success or a successful ``--check-policy`` report (report-not-gate + semantic per design §10 Q5). +- 1 — config error (unknown ``row-id``, no matching run-scoped target, + missing corpus, conflicting flags, an unloadable ``--config``). - 2 — runtime error (I/O, permission denied, atomic rename failure). """ @@ -88,7 +88,7 @@ _ROW_ID_KEYS: Tuple[str, ...] = ("id", "row_id") # Allowed --kind values per design §4.1. ``logs`` is intentionally -# absent (audit logs are append-only Article 17(3)(b) and never deleted +# absent (the audit log is append-only and never deleted # automatically — the trainer + the tool record the request, the # operator handles the residual disposal manually). _VALID_RUN_KINDS: Tuple[str, ...] = ("staging", "artefacts") @@ -251,7 +251,7 @@ def _validate_row_id_args(args, output_format: str) -> None: _output_error_and_exit( output_format, "`--row-id` requires `--corpus `. Bare row-id invocations are " - "rejected per Article 17 (per-row decision + per-row audit event).", + "rejected so that every erasure names the file it edits and gets its own audit event.", EXIT_CONFIG_ERROR, ) # ``--corpus`` is trusted operator self-service input (named directly on the @@ -386,9 +386,9 @@ def _detect_warning_conditions( Per design §5.1: - - ``data.erasure_warning_memorisation`` when any ``final_model/`` - exists in the operator's output dir tree (a trained model may - have memorised the row). + - ``data.erasure_warning_memorisation`` when a ``final_model/`` + directory exists directly under the operator's output dir (a + trained model may have memorised the row). - ``data.erasure_warning_synthetic_data_present`` when any ``synthetic_data*.jsonl`` exists (the row may have produced derivative snippets). @@ -865,17 +865,13 @@ def _run_purge_run_id(args, output_format: str) -> None: target_paths = _resolve_run_kind_targets(output_dir, args.run_id, args.kind) if not target_paths: - audit.log_event( - _EVT_ERASURE_FAILED, - **request_fields, - error_class="NoMatchingArtefacts", - error_message=f"No {args.kind!r} artefacts found for run_id={args.run_id!r}.", - ) - _output_error_and_exit( - output_format, - f"No {args.kind!r} artefacts found for run_id={args.run_id!r} under {output_dir!r}.", - EXIT_CONFIG_ERROR, + # The standard compliance bundle has fixed filenames with no run id, so a real run id never matches it. + hint = " The standard compliance bundle has fixed filenames without a run id; remove it through your storage layer." + reason = f"No {args.kind!r} artefacts found for run_id={args.run_id!r}." + ( + hint if args.kind == "artefacts" else "" ) + audit.log_event(_EVT_ERASURE_FAILED, **request_fields, error_class="NoMatchingArtefacts", error_message=reason) + _output_error_and_exit(output_format, f"{reason} (searched under {output_dir!r})", EXIT_CONFIG_ERROR) # Boundary check (F-P5-OPUS-02): refuse any resolved target that escapes # ``output_dir``. ``--run-id`` is free-form, so a ``..``-bearing value @@ -1010,11 +1006,11 @@ def _legacy_staging_owned_by(legacy_dir: Path, run_id: str) -> bool: def _artefact_targets_for_run(base: Path, run_id: str) -> List[Path]: """Return ``compliance/`` files whose name embeds ``run_id`` as a token. - Compliance bundle filenames embed the run-id; we accept both - ``compliance_.json`` and ``annex_iv_.json``. The - token-boundary check (`_filename_contains_run_id`) defends against - short-run-id false positives where a substring match would - accidentally pick up files belonging to a different run. + Any regular file directly under ``compliance/`` whose name carries the + run-id as a token matches, whatever its extension. The standard bundle + (fixed basenames such as ``compliance_report.json``) has no run-id, so a real + run id never matches it (a generic token such as ``json`` does). The token-boundary check (`_filename_contains_run_id`) + defends against short-run-id false positives on other runs' files. """ compliance_dir = base / "compliance" if not compliance_dir.is_dir(): @@ -1120,9 +1116,9 @@ def _delete_path(path: Path) -> int: def _run_purge_check_policy(args, output_format: str) -> None: """Handle ``forgelm purge --check-policy [--config ]``. - Always exits 0 (per design §10 Q5: report-not-gate semantic). - Operators wiring a CI gate use ``--output-format json`` and pipe - to ``jq '.violations | length'`` themselves. + A successful report exits 0 (design §10 Q5: report-not-gate); an unloadable config exits 1. + Operators wiring a CI gate (under ``set -o pipefail``) use ``--output-format json`` and pipe + to ``jq -e '.success == true and has("count") and .count == 0'``. Strict config loading: ``--check-policy`` is the one purge mode where the operator explicitly asked for a retention report. A diff --git a/forgelm/cli/subcommands/_reverse_pii.py b/forgelm/cli/subcommands/_reverse_pii.py index 84eb82bc..e07ac27a 100644 --- a/forgelm/cli/subcommands/_reverse_pii.py +++ b/forgelm/cli/subcommands/_reverse_pii.py @@ -484,10 +484,11 @@ def _hash_for_audit(query: str, salt: bytes) -> str: is set, the env secret too). Salt-free SHA-256 of low-entropy identifiers (e-mails, phone numbers) is brute-forcible from a commodity wordlist; the salt closes that gap. - 2. A purge → reverse-pii cycle for the same subject in the same - ``output_dir`` produces matching digests, so a compliance - reviewer correlating Article 17 + Article 15 events for one - subject sees a connected timeline. + 2. A purge → reverse-pii cycle for the same value (the ``--row-id`` + and the ``--query`` text) in the same ``output_dir`` and under the + same ``FORGELM_AUDIT_SECRET`` state produces matching digests, so a + compliance reviewer correlating Article 17 + Article 15 events sees a + connected timeline. F-W3FU-08 (priv) fix: ``salt`` is required (was ``Optional[bytes]`` with an unsalted-fallback branch). The legacy ``None`` branch was diff --git a/forgelm/config.py b/forgelm/config.py index 27f644ca..970656c7 100644 --- a/forgelm/config.py +++ b/forgelm/config.py @@ -1001,7 +1001,7 @@ class SafetyConfig(BaseModel): default=False, description=( "Persist raw `prompt` / `response` strings to `safety_results.json`. " - "Default OFF for GDPR / EU AI Act Art. 10 privacy by default — adversarial " + "Default OFF for GDPR Art. 25(2) data protection by default — adversarial " "test prompts and model responses may surface sensitive content. Opt in " "for debugging." ), @@ -1140,8 +1140,8 @@ class JudgeConfig(BaseModel): default=False, description=( "Persist raw eval `prompt`, `response`, and judge `reason` strings to " - "`judge_results.json`. Default OFF for GDPR / EU AI Act Art. 10 " - "privacy by default — judge reasoning can quote PII from the eval set. " + "`judge_results.json`. Default OFF for GDPR Art. 25(2) " + "data protection by default — judge reasoning can quote PII from the eval set. " "Opt in for debugging." ), ) @@ -1588,22 +1588,22 @@ class RetentionConfig(BaseModel): erasure horizons. Top-level retention block (per Phase 20 design `gdpr-erasure-design` - §3 + closure-plan §15.5 v2.5). All four horizons default to values - chosen to be compliant out of the box for typical enterprise EU AI - Act use: audit logs at 5 years (Article 12 record-keeping - obligation × statute-of-limitations buffer), staging at 7 days - (operator gets one work-week to act on a `forgelm reject`), - ephemeral artefacts at 90 days (compliance bundle + audit reports - have a quarterly review cadence in most QMS), raw documents at 90 - days (typical ingestion-window before re-running data audit). + §3 + closure-plan §15.5 v2.5). All four horizons default to + ForgeLM's own choices, not legal periods (GDPR Article 5(1)(e) sets + no fixed period): audit logs at 5 years, staging at 7 days + (one work-week before `purge --check-policy` reports a staging dir), + ephemeral artefacts at 90 days and raw documents at 90 days (round + numbers chosen for ForgeLM, not review cadences or ingestion windows + taken from any standard). + Choose values from your own necessity assessment. Setting any horizon to ``0`` disables the policy for that artefact - kind (retain indefinitely). The horizons are *reported*, not enforced: + kind (it is never reported). The horizons are *reported*, not enforced: ``forgelm purge --check-policy`` lists artefacts past them and always exits 0; nothing is deleted, blocked or scheduled automatically. ``enforce`` is reserved: no command acts on it yet, so ``warn_on_excess`` and ``block_on_excess`` behave like ``log_only`` - (the CLI logs a warning when a config sets either). + (training and ``--dry-run`` log a warning when a config sets either). """ model_config = ConfigDict(extra="forbid") @@ -1612,16 +1612,16 @@ class RetentionConfig(BaseModel): default=1825, ge=0, description=( - "Days to retain `audit_log.jsonl` before flagging it as overdue under Article 5(1)(e). " - "Default 1825 = 5 years. Set to 0 to retain indefinitely (Article 17(3)(b) defence)." + "Days after which `forgelm purge --check-policy` reports `audit_log.jsonl` as overdue (Article 5(1)(e)). " + "Default 1825 = 5 years, a ForgeLM choice, not a legal period. Set to 0 to stop reporting it; keep the log only while it stays necessary or a nameable legal duty requires it." ), ) staging_ttl_days: int = Field( default=7, ge=0, description=( - "Days to retain `final_model.staging./` after a `forgelm reject` decision before scheduled cleanup. " - "Set to 0 to retain indefinitely. This is the canonical staging-retention field; " + "Days after which `forgelm purge --check-policy` reports a `final_model.staging./` (or legacy `final_model.staging/`) directory as overdue (aged from the run's first audit event, else the log's first event, else mtime); nothing is deleted. " + "Set to 0 to stop reporting it. This is the canonical staging-retention field; " "the legacy `evaluation.staging_ttl_days` alias was removed in v0.8.0." ), ) @@ -1629,25 +1629,25 @@ class RetentionConfig(BaseModel): default=90, ge=0, description=( - "Days to retain compliance bundles, data audit reports, and other run-scoped derived artefacts. " - "Set to 0 to retain indefinitely." + "Age in days after which `forgelm purge --check-policy` reports `/compliance/` and " + "`/data_audit_report.json` as overdue (aged from the audit log's first event, else mtime); nothing is deleted. Set to 0 to stop reporting it." ), ) raw_documents_retention_days: int = Field( default=90, ge=0, description=( - "Days to retain ingested raw documents (PDF / DOCX / EPUB / TXT / Markdown) under " - "the operator's ingestion-output directory. Set to 0 to retain indefinitely. " - "Closes ghost-features GH-023 (was nested as `ingestion.retention.raw_documents.ttl_days`; now top-level)." + "Age in days after which `forgelm purge --check-policy` reports `/raw_documents/` or " + "`/ingestion_output/` (ingested PDF / DOCX / EPUB / TXT / Markdown) as overdue (aged from the audit log's first event, else mtime); nothing is deleted. Set to 0 to stop reporting it. " + "ForgeLM does not create these directories; the horizon applies only to source documents you keep there." ), ) enforce: Literal["log_only", "warn_on_excess", "block_on_excess"] = Field( default="log_only", description=( - "Reserved: no command reads it yet. Retention horizons are only reported, read-only, by `forgelm purge " - "--check-policy` (always exit 0); nothing is deleted, warned about or blocked automatically. " - "`warn_on_excess` and `block_on_excess` are accepted but behave like `log_only`; the CLI warns when set." + "Reserved: no command acts on it. Retention horizons are only reported, read-only, by `forgelm purge " + "--check-policy` (exit 0 whatever it finds); nothing is deleted, warned about or blocked automatically. " + "`warn_on_excess` and `block_on_excess` are accepted but behave like `log_only`; training and `--dry-run` warn when set." ), ) @@ -1699,7 +1699,7 @@ class ForgeConfig(BaseModel): ) retention: Optional[RetentionConfig] = Field( default=None, - description="Phase 21 / GDPR Article 5(1)(e) storage limitation + Article 17 erasure horizons block.", + description="Retention horizons (GDPR Article 5(1)(e) storage limitation), reported by `forgelm purge --check-policy` and never enforced.", ) pipeline: Optional["PipelineConfig"] = Field( default=None, diff --git a/forgelm/data_audit/_optional.py b/forgelm/data_audit/_optional.py index d8becea5..20ff67e1 100644 --- a/forgelm/data_audit/_optional.py +++ b/forgelm/data_audit/_optional.py @@ -66,7 +66,7 @@ # the regex detector so the default audit keeps its zero-extra-deps # guarantee. Presidio + spaCy are heavyweight (~50 MB model download), # so we stay strictly opt-in and fail soft when missing — the regex set -# already covers the GDPR-mandated structured identifiers (email, phone, +# already covers the common structured identifiers (email, phone, # IBAN, credit card, national IDs) that the audit's compliance contract # is built around. ML adds the *unstructured* identifiers regex inherently # misses: person names, organizations, locations. diff --git a/forgelm/data_audit/_pii_regex.py b/forgelm/data_audit/_pii_regex.py index 70937752..775b821c 100644 --- a/forgelm/data_audit/_pii_regex.py +++ b/forgelm/data_audit/_pii_regex.py @@ -1,8 +1,8 @@ """Regex-based PII detector + masker. -Prefix-anchored / shape-anchored patterns covering the GDPR-mandated +Prefix-anchored / shape-anchored patterns covering the common structured identifiers (email, phone, IBAN, credit card, national IDs). -These are the categories every audit *must* surface; the optional +These are the categories every audit surfaces by default; the optional Presidio ML-NER adapter (:mod:`forgelm.data_audit._pii_ml`) layers on top to pick up unstructured identifiers (person names, organizations, locations) which regex inherently misses. diff --git a/forgelm/data_audit/_types.py b/forgelm/data_audit/_types.py index c63e4512..0faf707b 100644 --- a/forgelm/data_audit/_types.py +++ b/forgelm/data_audit/_types.py @@ -22,12 +22,14 @@ # Phase 11.5: PII severity grading. A flat ``{type: count}`` map gives zero # guidance to a compliance reviewer staring at the audit JSON — they have to # remember by hand that ``credit_card`` is materially worse than ``phone``. -# The tiers below encode the consensus regulatory weighting (PCI-DSS for -# financial; GDPR Art. 9 + ENISA for identifiers) so the audit surfaces a -# one-glance verdict via :data:`PII_SEVERITY`. Tweak the table when local -# law disagrees — the audit honours whatever map is loaded at call time. +# The tiers below are ForgeLM's default weighting, not a regulatory +# classification (PCI DSS protects card numbers; GDPR Art. 87 lets Member +# States set the specific conditions for processing national identification +# numbers), so the audit surfaces a +# one-glance verdict via :data:`PII_SEVERITY`. Tweak the table when local law +# disagrees — the audit honours whatever map is loaded at call time. PII_SEVERITY: Dict[str, str] = { - # Financial / fully reversible identity theft -> highest weight. + # Financial identifiers -> highest weight: directly usable for fraud. "credit_card": "critical", "iban": "critical", # Government-issued identifiers -> high. Tied to a specific person and diff --git a/forgelm/ingestion.py b/forgelm/ingestion.py index 199a0d98..8f252328 100644 --- a/forgelm/ingestion.py +++ b/forgelm/ingestion.py @@ -2534,7 +2534,7 @@ def _maybe_sample(payload: str) -> None: breakdown = ", ".join(f"{k}={v}" for k, v in sorted(pii_redaction_counts.items())) notes.append(f"PII masking redacted {redacted_total} span(s): {breakdown}") else: - notes.append("PII masking enabled — no PII detected in this corpus") + notes.append("PII masking enabled — the regex detectors found nothing to mask (names are not detected)") if secrets_mask: if secrets_redaction_counts: secret_total = sum(secrets_redaction_counts.values()) diff --git a/forgelm/wizard/_collectors.py b/forgelm/wizard/_collectors.py index d7574192..a28f76d3 100644 --- a/forgelm/wizard/_collectors.py +++ b/forgelm/wizard/_collectors.py @@ -841,19 +841,19 @@ def _collect_retention() -> Optional[Dict[str, Any]]: ): return None audit = _prompt_int( - "audit_log_retention_days (0 = retain indefinitely)", + "audit_log_retention_days (0 = never report)", 1825, min_val=0, max_val=18250, ) - staging = _prompt_int("staging_ttl_days (0 = retain indefinitely)", 7, min_val=0, max_val=365) + staging = _prompt_int("staging_ttl_days (0 = never report)", 7, min_val=0, max_val=365) ephemeral = _prompt_int( - "ephemeral_artefact_retention_days (0 = retain indefinitely)", + "ephemeral_artefact_retention_days (0 = never report)", 90, min_val=0, max_val=3650, ) - raw_docs = _prompt_int("raw_documents_retention_days (0 = retain indefinitely)", 90, min_val=0, max_val=3650) + raw_docs = _prompt_int("raw_documents_retention_days (0 = never report)", 90, min_val=0, max_val=3650) enforce = _prompt_choice( "enforcement mode (reserved: no command acts on it yet; `forgelm purge --check-policy` only reports):", [ diff --git a/notebooks/data_curation.ipynb b/notebooks/data_curation.ipynb index 00892a4d..a492f4db 100644 --- a/notebooks/data_curation.ipynb +++ b/notebooks/data_curation.ipynb @@ -373,7 +373,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## What's next\n\n- **Train on the curated JSONL**: point any of the training notebooks at\n `./curated/policies.jsonl` via `data.dataset_name_or_path`.\n - [`quickstart_sft.ipynb`](./quickstart_sft.ipynb) — first SFT run\n - [`multi_dataset.ipynb`](./multi_dataset.ipynb) — mix the curated\n set with public datasets\n- **Long-form guides**:\n - [`docs/guides/ingestion.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/guides/ingestion.md)\n — every flag, every chunking strategy, every extra\n - [`docs/guides/data_audit.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/guides/data_audit.md)\n — audit JSON schema, severity tiers, governance integration\n- **Wizard**: `forgelm --wizard` offers to run `forgelm ingest` inline\n when you point it at a directory of raw docs, and offers to run\n `forgelm audit` automatically once a JSONL is selected — closes the\n BYOD audit loop end-to-end.\n- **Other v0.5.0 audit add-ons**:\n - `--croissant` emits a [Google Croissant 1.0](http://mlcommons.org/croissant/)\n dataset card under the report's `croissant` key so the same JSON\n file doubles as both the EU AI Act Article 10 governance artifact\n and a Croissant-consumer dataset card.\n - `--pii-ml [--pii-ml-language LANG]` layers\n [Presidio](https://github.com/data-privacy-stack/presidio) NER on top of\n the regex detector — adds `person` / `organization` /\n `location` categories to `pii_summary` / `pii_severity`. **Two-step\n install:**\n ```bash\n pip install 'forgelm[ingestion-pii-ml]'\n python -m spacy download en_core_web_lg\n ```\n `presidio-analyzer` does not transitively ship a spaCy NER model;\n without the model `--pii-ml` raises an `ImportError` with the\n install recipe **before** any rows are scanned. Pass\n `--pii-ml-language tr` (and download the matching spaCy model) to\n audit Turkish corpora." + "source": "## What's next\n\n- **Train on the curated JSONL**: point any of the training notebooks at\n `./curated/policies.jsonl` via `data.dataset_name_or_path`.\n - [`quickstart_sft.ipynb`](./quickstart_sft.ipynb) — first SFT run\n - [`multi_dataset.ipynb`](./multi_dataset.ipynb) — mix the curated\n set with public datasets\n- **Long-form guides**:\n - [`docs/guides/ingestion.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/guides/ingestion.md)\n — every flag, every chunking strategy, every extra\n - [`docs/guides/data_audit.md`](https://github.com/HodeTech/ForgeLM/blob/main/docs/guides/data_audit.md)\n — audit JSON schema, severity tiers, governance integration\n- **Wizard**: `forgelm --wizard` offers to run `forgelm ingest` inline\n when you point it at a directory of raw docs, and offers to run\n `forgelm audit` automatically once a JSONL is selected — closes the\n BYOD audit loop end-to-end.\n- **Other v0.5.0 audit add-ons**:\n - `--croissant` emits a [Google Croissant 1.0](http://mlcommons.org/croissant/)\n dataset card under the report's `croissant` key so the same JSON\n file doubles as both the EU AI Act Article 10 governance artifact\n and a Croissant-consumer dataset card.\n - `--pii-ml [--pii-ml-language LANG]` layers\n [Presidio](https://github.com/data-privacy-stack/presidio) NER on top of\n the regex detector — adds `person` / `organization` /\n `location` categories to `pii_summary` / `pii_severity`. **Two-step\n install:**\n ```bash\n pip install 'forgelm[ingestion-pii-ml]'\n python -m spacy download en_core_web_lg\n ```\n `presidio-analyzer` does not transitively ship a spaCy NER model;\n without the model `--pii-ml` raises an `ImportError` with the\n install recipe **before** any rows are scanned. For a\n non-English corpus pass `--pii-ml-language` with a supported code\n (for example `de`) and download the matching spaCy model; Turkish has\n no dedicated model, so use `--pii-ml-language xx` with\n `xx_ent_wiki_sm`." } ], "metadata": { diff --git a/notebooks/ingestion_playground.ipynb b/notebooks/ingestion_playground.ipynb index 9ae8f151..7159c660 100644 --- a/notebooks/ingestion_playground.ipynb +++ b/notebooks/ingestion_playground.ipynb @@ -103,7 +103,7 @@ "|---|---|\n", "| `STRATEGY` | `\"paragraph\"` (default) / `\"sliding\"` / `\"markdown\"`. Markdown is heading-aware and best for `.md` corpora. Paragraph is the safe default for mixed corpora. |\n", "| `CHUNK_SIZE` | Soft per-chunk character cap (default 2048). 600–1024 is good for tight LMs (≤4k context); 4096 for bigger LMs. |\n", - "| `MASK_PII` | Redact names / emails / phone numbers / IDs before writing. |\n", + "| `MASK_PII` | Redact the formats the regex layer detects (emails, phone numbers, card / IBAN / national-ID formats; **not names**) before writing. |\n", "| `MASK_SECRETS` | Redact API keys / tokens / SSH keys / connection strings. |\n", "| `RECURSIVE` | Descend into subdirectories of `./raw_docs/`. |\n", "\n", @@ -132,7 +132,7 @@ "# corpora, \"en\" for English, etc. Leave as None to disable the check.\n", "LANGUAGE_HINT = None # e.g. \"tr\" for a Turkish corpus\n", "\n", - "MASK_PII = True # redact PII (names, emails, phones, IDs)\n", + "MASK_PII = True # redact detected emails, phones, cards, IBANs, IDs (not names)\n", "MASK_SECRETS = True # redact API keys, tokens, SSH/PGP keys, JWTs\n", "RECURSIVE = True # descend into subdirectories\n", "OUTPUT_PATH = \"./curated/dataset.jsonl\" # where the curated JSONL lands\n", diff --git a/pyproject.toml b/pyproject.toml index 6c754305..5db58fed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -172,7 +172,7 @@ ingestion-scale = [ # categories (person / organization / location) that the regex detector # inherently misses. Heavyweight install (≈ 50 MB spaCy model on first # analyzer build), so opt-in only — the regex set already covers the -# GDPR-mandated structured identifiers (email, phone, IBAN, credit card, +# common structured identifiers (email, phone, IBAN, credit card, # national IDs). ingestion-pii-ml = [ "presidio-analyzer>=2.2.0,<3.0.0", diff --git a/site/compliance.html b/site/compliance.html index b4846663..c7e10c80 100644 --- a/site/compliance.html +++ b/site/compliance.html @@ -146,7 +146,7 @@

Risk management

Article 10

Data & data governance

-

Quality, relevance, representativeness criteria. Bias detection, PII redaction. Optional Presidio ML-NER for unstructured PII; optional Croissant 1.0 dataset card emitted alongside the audit.

+

Quality, relevance, representativeness criteria you declare. PII counts and masking, cross-split leakage report. Optional Presidio ML-NER PII layer and Croissant 1.0 dataset card emitted alongside the audit. Every audit report can be filed as part of your Article 10 evidence; collection, provenance and bias-examination documentation still come from you.

forgelm/data_audit/ · forgelm/ingestion.py
@@ -202,14 +202,14 @@

PII redaction at ingest

GDPR Art. 15

Right of access

-

forgelm reverse-pii scans masked JSONL corpora for plaintext residuals or salted SHA-256 digests of a data-subject identifier and reports every row of every split where that subject appears. It finds only what is literally in the files you scan: raw sources and the trained model are not searched, and handling an access request stays your process.

+

forgelm reverse-pii searches JSONL corpora for a data-subject identifier, as plain text or as a salted SHA-256 digest written by an external masking pipeline, and reports every line in the scanned files that contains it. By default the match is literal and case-sensitive (--type custom takes a regex). It is an input to your Article 15 response, which must also give a copy of the data and the Article 15(1)(a)-(h) information; raw sources and the trained model are not searched.

forgelm reverse-pii
GDPR Art. 17

Right to erasure

-

forgelm purge deletes a data-subject's rows from training corpora and stamps data.erasure_requested + data.erasure_completed events into the append-only audit log so post-deletion runs are forensically linked to the erasure event.

+

forgelm purge deletes rows by id from a training corpus and records data.erasure_requested + data.erasure_completed events in the append-only audit log. It is one step of an Article 17 erasure: trained weights, source documents and copies elsewhere are not changed.

forgelm purge
@@ -277,7 +277,7 @@

A run's compliance footprint

checkpoints/policy-bot/             # training.output_dir from your YAML
 ├── audit_log.jsonl                 # Article 12 append-only event log
 ├── audit_log.jsonl.manifest.json   # SHA-256 sidecar over the audit log
-├── data_audit_report.json          # Article 10 quality/PII/bias scan — you place it here (see note)
+├── data_audit_report.json          # Article 10 quality/PII/leakage audit — you place it here (see note)
 ├── compliance/                     # Annex IV bundle (Articles 9-17)
 │   ├── annex_iv_metadata.json      # Article 11 technical documentation (flat JSON)
 │   ├── compliance_report.json      # Full Article 9-17 manifest — superset of the per-artefact files
diff --git a/site/features.html b/site/features.html
index 65307521..cc574d69 100644
--- a/site/features.html
+++ b/site/features.html
@@ -414,13 +414,13 @@ 

Article 14 staging gate

-

GDPR Article 17 erasure

-

forgelm purge drops a data-subject's rows from training corpora and stamps the deletion in the append-only audit log.

+

Row erasure (Article 17 step)

+

forgelm purge deletes rows by id from a training corpus and records the erasure in the append-only audit log; it is one step of an Article 17 erasure (trained weights are not changed).

-

GDPR Article 15 access

-

forgelm reverse-pii scans masked JSONL corpora for forensic substring or salted-digest matches that surface every row in which a data-subject identifier appears.

+

Identifier search (Article 15 input)

+

forgelm reverse-pii scans JSONL corpora for plaintext or salted-digest matches of a data-subject identifier and lists the lines in the scanned files that contain it: an input to an Article 15 response.

diff --git a/site/index.html b/site/index.html index dce4c389..02e92654 100644 --- a/site/index.html +++ b/site/index.html @@ -536,7 +536,7 @@

ISO 27001 / SOC 2 alignment

GDPR rights tooling

-

Article 17 erasure with forgelm purge and Article 15 access with forgelm reverse-pii, both wired into the audit log.

+

forgelm reverse-pii finds the lines that contain an identifier you supply (an input to an Article 15 response); forgelm purge deletes rows by id from training corpora (one step of an Article 17 erasure; trained weights are not changed). Both are recorded in the audit log.

forgelm purge forgelm reverse-pii @@ -637,7 +637,7 @@

Use cases that actually ship

Domain expert from policy PDFs

-

Drop your regulatory corpus into forgelm ingest, audit it, and SFT a model that answers in your domain's voice — with common PII formats masked at ingest, before training.

+

Drop your regulatory corpus into forgelm ingest, audit it, and SFT a model that answers in your domain's voice — with common PII formats masked at ingest (opt in with --pii-mask), before training.

forgelm quickstart domain-expert
diff --git a/site/js/translations.js b/site/js/translations.js index ac982eb6..756d1161 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -43,7 +43,7 @@ "common.footer.issues": "Issues", "common.footer.discussions":"Discussions", "common.footer.releases": "Releases", - "common.legal.last_updated":"Last updated: 2026-05-09 · This is a plain-language summary, not legal advice." + "common.legal.last_updated":"Last updated: 2026-10-22 · This is a plain-language summary, not legal advice." }; T.tr = { @@ -71,7 +71,7 @@ "common.footer.issues": "Sorunlar", "common.footer.discussions":"Tartışmalar", "common.footer.releases": "Sürümler", - "common.legal.last_updated":"Son güncelleme: 09-05-2026 · Bu hukuki tavsiye değil, açık dilde özettir." + "common.legal.last_updated":"Son güncelleme: 22-10-2026 · Bu hukuki tavsiye değil, açık dilde özettir." }; T.de = { @@ -99,7 +99,7 @@ "common.footer.issues": "Issues", "common.footer.discussions":"Diskussionen", "common.footer.releases": "Releases", - "common.legal.last_updated":"Letzte Aktualisierung: 09.05.2026 · Eine verständliche Zusammenfassung, keine Rechtsberatung." + "common.legal.last_updated":"Letzte Aktualisierung: 22.10.2026 · Eine verständliche Zusammenfassung, keine Rechtsberatung." }; T.fr = { @@ -127,7 +127,7 @@ "common.footer.issues": "Issues", "common.footer.discussions":"Discussions", "common.footer.releases": "Versions", - "common.legal.last_updated":"Dernière mise à jour : 09/05/2026 · Résumé en langage clair, pas un conseil juridique." + "common.legal.last_updated":"Dernière mise à jour : 22/10/2026 · Résumé en langage clair, pas un conseil juridique." }; T.es = { @@ -155,7 +155,7 @@ "common.footer.issues": "Issues", "common.footer.discussions":"Discusiones", "common.footer.releases": "Versiones", - "common.legal.last_updated":"Última actualización: 09-05-2026 · Resumen en lenguaje sencillo; no es asesoramiento legal." + "common.legal.last_updated":"Última actualización: 22-10-2026 · Resumen en lenguaje sencillo; no es asesoramiento legal." }; T.zh = { @@ -183,7 +183,7 @@ "common.footer.issues": "Issues", "common.footer.discussions":"讨论", "common.footer.releases": "版本", - "common.legal.last_updated":"最后更新:2026-05-09 · 通俗语言总结,非法律建议。" + "common.legal.last_updated":"最后更新:2026-10-22 · 通俗语言总结,非法律建议。" }; /* ── Page-specific blocks below extend each language with Object.assign. ── */ @@ -390,7 +390,7 @@ "home.cap.iso.title": "ISO 27001 / SOC 2 alignment", "home.cap.iso.body": "Audit-trail, change-management, data-lineage, and supply-chain evidence the deployer's auditor asks for. Software is aligned, not certified.", "home.cap.gdpr.title": "GDPR rights tooling", - "home.cap.gdpr.body": "Article 17 erasure with forgelm purge and Article 15 access with forgelm reverse-pii, both wired into the audit log.", + "home.cap.gdpr.body": "forgelm reverse-pii finds the lines that contain an identifier you supply (an input to an Article 15 response); forgelm purge deletes rows by id from training corpora (one step of an Article 17 erasure; trained weights are not changed). Both are recorded in the audit log.", "home.cap.see_all": "See the full feature matrix", "home.yaml.eyebrow": "A real config — not a screenshot", "home.yaml.title": "One YAML, end-to-end run", @@ -398,7 +398,7 @@ "home.uses.eyebrow": "Built for these workflows", "home.uses.title": "Use cases that actually ship", "home.use.domain.title": "Domain expert from policy PDFs", - "home.use.domain.body": "Drop your regulatory corpus into forgelm ingest, audit it, and SFT a model that answers in your domain's voice — with common PII formats masked at ingest, before training.", + "home.use.domain.body": "Drop your regulatory corpus into forgelm ingest, audit it, and SFT a model that answers in your domain's voice — with common PII formats masked at ingest (opt in with --pii-mask), before training.", "home.use.support.title": "Customer support assistant", "home.use.support.body": "SFT on past tickets (and DPO on chosen/rejected agent feedback when you have it), Llama Guard safety evaluation gate, auto-revert if helpfulness drops. Ship to vLLM with one command.", "home.use.code.title": "Code copilot fine-tune", @@ -496,7 +496,7 @@ "home.cap.iso.title": "ISO 27001 / SOC 2 hizalaması", "home.cap.iso.body": "Deployer denetçisinin sorduğu audit izi, change-management, veri kökenleri ve tedarik zinciri kanıtları. Yazılım sertifikalı değil, hizalanmıştır.", "home.cap.gdpr.title": "GDPR hakları araçları", - "home.cap.gdpr.body": "forgelm purge ile Madde 17 silme ve forgelm reverse-pii ile Madde 15 erişim — her ikisi de audit log'a bağlı.", + "home.cap.gdpr.body": "forgelm reverse-pii verdiğiniz bir tanımlayıcıyı içeren satırları bulur (Madde 15 yanıtının bir girdisi); forgelm purge eğitim corpus'larından satırları id ile siler (Madde 17 kapsamındaki silmenin bir adımı; eğitilmiş ağırlıklar değişmez). İkisi de audit log'a kaydedilir.", "home.cap.see_all": "Tüm özellik matrisini gör", "home.yaml.eyebrow": "Gerçek bir konfigürasyon — ekran görüntüsü değil", "home.yaml.title": "Bir YAML, uçtan uca koşu", @@ -504,7 +504,7 @@ "home.uses.eyebrow": "Bu iş akışları için tasarlandı", "home.uses.title": "Gerçekten üretime çıkan kullanım senaryoları", "home.use.domain.title": "Politika PDF'lerinden alan uzmanı", - "home.use.domain.body": "Regülasyon korpusunuzu forgelm ingest'e bırakın, denetleyin ve alanınızın diliyle cevap veren bir modeli SFT'leyin — yaygın PII biçimleri ingest sırasında, eğitimden önce maskelenir.", + "home.use.domain.body": "Regülasyon korpusunuzu forgelm ingest'e bırakın, denetleyin ve alanınızın diliyle cevap veren bir modeli SFT'leyin — yaygın PII biçimleri ingest sırasında, eğitimden önce maskelenir (--pii-mask ile açılır).", "home.use.support.title": "Müşteri destek asistanı", "home.use.support.body": "Geçmiş ticket'larda SFT (eğer chosen/rejected agent geri bildirimi varsa DPO eklenebilir), Llama Guard güvenlik değerlendirme kapısı, yardımseverlik düşerse otomatik artefakt silme. Tek komutla vLLM'e gönderim.", "home.use.code.title": "Kod copilot fine-tune", @@ -602,7 +602,7 @@ "home.cap.iso.title": "ISO 27001 / SOC 2 Alignment", "home.cap.iso.body": "Audit-Trail, Change-Management, Daten-Lineage und Supply-Chain-Nachweise, nach denen der Auditor des Deployers fragt. Software ist aligned, nicht zertifiziert.", "home.cap.gdpr.title": "DSGVO-Rechtetooling", - "home.cap.gdpr.body": "Artikel-17-Löschung mit forgelm purge und Artikel-15-Auskunft mit forgelm reverse-pii — beide ans Audit-Log angebunden.", + "home.cap.gdpr.body": "forgelm reverse-pii findet die Zeilen, die eine von Ihnen angegebene Kennung enthalten (ein Baustein der Auskunft nach Art. 15 DSGVO); forgelm purge löscht Zeilen anhand ihrer ID aus Trainingskorpora (ein Schritt der Löschung nach Art. 17 DSGVO; trainierte Gewichte bleiben unverändert). Beide Vorgänge werden im Audit-Log festgehalten.", "home.cap.see_all": "Vollständige Funktionsmatrix anzeigen", "home.yaml.eyebrow": "Eine echte Konfiguration — kein Screenshot", "home.yaml.title": "Ein YAML, ein Ende-zu-Ende-Lauf", @@ -610,7 +610,7 @@ "home.uses.eyebrow": "Gebaut für diese Workflows", "home.uses.title": "Use-Cases, die wirklich in Produktion gehen", "home.use.domain.title": "Domänen-Experte aus Policy-PDFs", - "home.use.domain.body": "Werfen Sie Ihren regulatorischen Korpus in forgelm ingest, auditieren Sie ihn und SFTen Sie ein Modell, das in der Sprache Ihrer Domäne antwortet — mit beim Ingest, vor dem Training, maskierten gängigen PII-Formaten.", + "home.use.domain.body": "Werfen Sie Ihren regulatorischen Korpus in forgelm ingest, auditieren Sie ihn und SFTen Sie ein Modell, das in der Sprache Ihrer Domäne antwortet — mit beim Ingest, vor dem Training, maskierten gängigen PII-Formaten (per --pii-mask zu aktivieren).", "home.use.support.title": "Kundensupport-Assistent", "home.use.support.body": "SFT auf vergangenen Tickets (und DPO auf gewählten/abgelehnten Agenten-Feedback, falls verfügbar), Llama-Guard-Sicherheitsbewertungs-Gate, automatisches Verwerfen der Artefakte, wenn die Hilfsbereitschaft sinkt. Mit einem Befehl an vLLM ausliefern.", "home.use.code.title": "Code-Copilot Fine-Tune", @@ -708,7 +708,7 @@ "home.cap.iso.title": "Alignement ISO 27001 / SOC 2", "home.cap.iso.body": "Piste d'audit, gestion du changement, lignée des données et preuves de chaîne logistique demandées par l'auditeur du déployeur. Le logiciel est aligné, pas certifié.", "home.cap.gdpr.title": "Outils de droits RGPD", - "home.cap.gdpr.body": "Effacement Article 17 avec forgelm purge et accès Article 15 avec forgelm reverse-pii — les deux connectés au journal d'audit.", + "home.cap.gdpr.body": "forgelm reverse-pii trouve les lignes qui contiennent un identifiant que vous fournissez (un élément de la réponse à une demande d'accès, article 15) ; forgelm purge supprime des lignes des corpus d'entraînement d'après leur identifiant (une étape de l'effacement, article 17 ; les poids entraînés ne changent pas). Les deux opérations sont consignées dans le journal d'audit.", "home.cap.see_all": "Voir la matrice complète des fonctionnalités", "home.yaml.eyebrow": "Une vraie configuration — pas une capture d'écran", "home.yaml.title": "Un YAML, exécution de bout en bout", @@ -716,7 +716,7 @@ "home.uses.eyebrow": "Conçu pour ces workflows", "home.uses.title": "Cas d'usage qui livrent vraiment", "home.use.domain.title": "Expert métier à partir de PDF de politiques", - "home.use.domain.body": "Déposez votre corpus réglementaire dans forgelm ingest, auditez-le, et fine-tunez en SFT un modèle qui répond avec la voix de votre domaine — avec les formats PII courants masqués à l'ingestion, avant l'entraînement.", + "home.use.domain.body": "Déposez votre corpus réglementaire dans forgelm ingest, auditez-le, et fine-tunez en SFT un modèle qui répond avec la voix de votre domaine — avec les formats PII courants masqués à l'ingestion, avant l'entraînement (à activer avec --pii-mask).", "home.use.support.title": "Assistant de support client", "home.use.support.body": "SFT sur tickets passés (et DPO sur feedbacks choisis/rejetés des agents si disponibles), gate d'évaluation sécurité Llama Guard, suppression automatique des artefacts si la serviabilité baisse. Livraison à vLLM en une commande.", "home.use.code.title": "Fine-tune copilote de code", @@ -814,7 +814,7 @@ "home.cap.iso.title": "Alineación con ISO 27001 / SOC 2", "home.cap.iso.body": "Pista de auditoría, gestión del cambio, linaje de datos y evidencias de cadena de suministro que pide el auditor del implementador. El software está alineado, no certificado.", "home.cap.gdpr.title": "Herramientas de derechos RGPD", - "home.cap.gdpr.body": "Borrado del Artículo 17 con forgelm purge y acceso del Artículo 15 con forgelm reverse-pii — ambos conectados al log de auditoría.", + "home.cap.gdpr.body": "forgelm reverse-pii encuentra las líneas que contienen un identificador que usted proporciona (un elemento de la respuesta a una solicitud de acceso, artículo 15); forgelm purge elimina filas por id de los corpus de entrenamiento (un paso de la supresión, artículo 17; los pesos entrenados no cambian). Ambas operaciones quedan registradas en el log de auditoría.", "home.cap.see_all": "Ver la matriz completa de características", "home.yaml.eyebrow": "Una configuración real — no una captura", "home.yaml.title": "Un YAML, ejecución de extremo a extremo", @@ -822,7 +822,7 @@ "home.uses.eyebrow": "Construido para estos flujos", "home.uses.title": "Casos de uso que realmente se despliegan", "home.use.domain.title": "Experto de dominio a partir de PDFs regulatorios", - "home.use.domain.body": "Suelte su corpus regulatorio en forgelm ingest, audítelo y haga SFT a un modelo que responda con la voz de su dominio — con los formatos de PII comunes enmascarados en la ingestión, antes del entrenamiento.", + "home.use.domain.body": "Suelte su corpus regulatorio en forgelm ingest, audítelo y haga SFT a un modelo que responda con la voz de su dominio — con los formatos de PII comunes enmascarados en la ingestión, antes del entrenamiento (se activa con --pii-mask).", "home.use.support.title": "Asistente de soporte al cliente", "home.use.support.body": "SFT sobre tickets pasados (y DPO sobre feedback elegido/rechazado de agentes si está disponible), puerta de evaluación de seguridad Llama Guard, descarte automático de artefactos si la utilidad baja. Despliegue a vLLM con un solo comando.", "home.use.code.title": "Fine-tune de copiloto de código", @@ -920,7 +920,7 @@ "home.cap.iso.title": "ISO 27001 / SOC 2 对齐", "home.cap.iso.body": "部署方审计员要求的审计轨迹、变更管理、数据沿袭和供应链证据。软件是对齐的,不是认证的。", "home.cap.gdpr.title": "GDPR 权利工具", - "home.cap.gdpr.body": "通过 forgelm purge 实现第 17 条删除,通过 forgelm reverse-pii 实现第 15 条访问 — 两者都接入审计日志。", + "home.cap.gdpr.body": "forgelm reverse-pii 查找包含你所提供标识符的行(可作为答复第 15 条访问请求的材料之一);forgelm purge 按 id 从训练语料中删除行(只是第 17 条删除流程中的一步,已训练的权重不会改变)。两者都会记录到审计日志。", "home.cap.see_all": "查看完整功能矩阵", "home.yaml.eyebrow": "真实配置 — 不是截图", "home.yaml.title": "一个 YAML,端到端运行", @@ -928,7 +928,7 @@ "home.uses.eyebrow": "为这些工作流而构建", "home.uses.title": "真正能上线的用例", "home.use.domain.title": "从政策 PDF 中得到的领域专家", - "home.use.domain.body": "把你的法规语料投入 forgelm ingest、审计它、然后 SFT 一个能用你领域口吻回答的模型 — 常见 PII 格式在 ingest 时,训练之前,就被屏蔽。", + "home.use.domain.body": "把你的法规语料投入 forgelm ingest、审计它、然后 SFT 一个能用你领域口吻回答的模型 — 常见 PII 格式在 ingest 时,训练之前,就被屏蔽(需通过 --pii-mask 开启)。", "home.use.support.title": "客户支持助手", "home.use.support.body": "在过往工单上 SFT (有 chosen/rejected 工单反馈时可叠加 DPO),Llama Guard 安全评估 gate,有用度下降时自动丢弃训练产物。一条命令交付到 vLLM。", "home.use.code.title": "代码 copilot 微调", @@ -983,7 +983,7 @@ "features.data.multi.title": "Multi-format ingest", "features.data.multi.body": "PDF, DOCX, EPUB, TXT, Markdown → SFT-ready JSONL. Markdown-aware splitter; DOCX tables emit as Markdown tables; PDF header/footer dedup.", "features.data.pii.title": "PII masking", - "features.data.pii.body": "Email, phone, credit card (Luhn-validated), IBAN, national IDs (TR / DE / FR / US-SSN). Masks at ingest time, before chunks land in JSONL.", + "features.data.pii.body": "Email, phone, credit card (Luhn-validated), IBAN, national IDs (TR / DE / FR / US-SSN). Opt in with --pii-mask: masks at ingest time, before chunks land in JSONL.", "features.data.secrets.title":"Secrets scrubbing", "features.data.secrets.body": "AWS keys, GitHub PATs, Slack / OpenAI / Google tokens, JWTs, full PEM private-key blocks (BEGIN…END), Azure storage strings — redacted with [REDACTED-SECRET].", "features.data.dup.title": "Near-duplicate detection", @@ -1032,10 +1032,10 @@ "features.gov.body": "EU AI Act Articles 9-17, GDPR Article 15 + 17, ISO 27001 / SOC 2 Type II alignment evidence.", "features.gov.approve.title": "Article 14 staging gate", "features.gov.approve.body": "forgelm approve / forgelm reject + forgelm approvals implement the human-oversight staging directory with audit-stamped sign-off.", - "features.gov.purge.title": "GDPR Article 17 erasure", - "features.gov.purge.body": "forgelm purge drops a data-subject's rows from training corpora and stamps the deletion in the append-only audit log.", - "features.gov.access.title": "GDPR Article 15 access", - "features.gov.access.body": "forgelm reverse-pii scans masked JSONL corpora for forensic substring or salted-digest matches that surface every row in which a data-subject identifier appears.", + "features.gov.purge.title": "Row erasure (Article 17 step)", + "features.gov.purge.body": "forgelm purge deletes rows by id from a training corpus and records the erasure in the append-only audit log; it is one step of an Article 17 erasure (trained weights are not changed).", + "features.gov.access.title": "Identifier search (Article 15 input)", + "features.gov.access.body": "forgelm reverse-pii scans JSONL corpora for plaintext or salted-digest matches of a data-subject identifier and lists the lines in the scanned files that contain it: an input to an Article 15 response.", "features.gov.annex.title": "Annex IV verifier", "features.gov.annex.body": "forgelm verify-annex-iv validates the Annex IV §1-9 schema completeness and the bundle's manifest_hash for tampering detection. (Audit-chain integrity is the separate forgelm verify-audit command.)", "features.gov.gguf.title": "GGUF integrity verifier", @@ -1088,7 +1088,7 @@ "features.data.multi.title": "Çoklu format ingest'i", "features.data.multi.body": "PDF, DOCX, EPUB, TXT, Markdown → SFT-hazır JSONL. Markdown-bilen splitter; DOCX tabloları Markdown tablo olarak; PDF header/footer dedup.", "features.data.pii.title": "PII maskeleme", - "features.data.pii.body": "E-posta, telefon, kredi kartı (Luhn doğrulamalı), IBAN, ulusal kimlik (TR / DE / FR / US-SSN). Chunk'lar JSONL'a düşmeden ingest sırasında maskelenir.", + "features.data.pii.body": "E-posta, telefon, kredi kartı (Luhn doğrulamalı), IBAN, ulusal kimlik (TR / DE / FR / US-SSN). Chunk'lar JSONL'a düşmeden ingest sırasında maskelenir (--pii-mask ile açılır).", "features.data.secrets.title":"Sırların temizlenmesi", "features.data.secrets.body": "AWS anahtarları, GitHub PAT'leri, Slack / OpenAI / Google token'ları, JWT'ler, tam PEM özel anahtar blokları (BEGIN…END), Azure storage string'leri — [REDACTED-SECRET] ile maskelenir.", "features.data.dup.title": "Near-duplicate tespiti", @@ -1137,10 +1137,10 @@ "features.gov.body": "EU AI Act Madde 9-17, GDPR Madde 15 + 17, ISO 27001 / SOC 2 Type II hizalama kanıtı.", "features.gov.approve.title": "Madde 14 staging kapısı", "features.gov.approve.body": "forgelm approve / forgelm reject + forgelm approvals, audit-damgalı onayla insan-gözetimli staging dizinini uygular.", - "features.gov.purge.title": "GDPR Madde 17 silme", - "features.gov.purge.body": "forgelm purge bir veri sahibinin satırlarını eğitim corpus'undan düşürür ve silmeyi append-only audit log'a damgalar.", - "features.gov.access.title": "GDPR Madde 15 erişim", - "features.gov.access.body": "forgelm reverse-pii maskelenmiş JSONL corpus'larını tarayarak veri sahibi tanımlayıcısının düz-metin kalıntılarına ya da tuzlanmış-digest eşleşmelerine bakar ve geçtiği her satırı yüzeye çıkarır.", + "features.gov.purge.title": "Satır silme (Madde 17 adımı)", + "features.gov.purge.body": "forgelm purge bir eğitim corpus'undan satırları id ile siler ve silmeyi append-only audit log'a kaydeder; Madde 17 kapsamındaki silmenin bir adımıdır (eğitilmiş ağırlıklar değişmez).", + "features.gov.access.title": "Tanımlayıcı arama (Madde 15 girdisi)", + "features.gov.access.body": "forgelm reverse-pii JSONL corpus'larını tarayarak bir veri sahibi tanımlayıcısının düz metin ya da salt'lı digest eşleşmelerini arar ve taranan dosyalarda onu içeren satırları listeler: Madde 15 yanıtının bir girdisi.", "features.gov.annex.title": "Annex IV doğrulayıcı", "features.gov.annex.body": "forgelm verify-annex-iv Annex IV §1-9 şema tamlığını ve bundle'ın manifest_hash'ini tampering tespiti için doğrular. (Audit-chain bütünlüğü ayrı bir forgelm verify-audit komutudur.)", "features.gov.gguf.title": "GGUF bütünlük doğrulayıcı", @@ -1193,7 +1193,7 @@ "features.data.multi.title": "Multi-Format-Ingest", "features.data.multi.body": "PDF, DOCX, EPUB, TXT, Markdown → SFT-fertiges JSONL. Markdown-bewusster Splitter; DOCX-Tabellen werden als Markdown-Tabellen ausgegeben; PDF-Header/Footer-Dedup.", "features.data.pii.title": "PII-Maskierung", - "features.data.pii.body": "E-Mail, Telefon, Kreditkarte (Luhn-validiert), IBAN, nationale IDs (TR / DE / FR / US-SSN). Maskiert beim Ingest, bevor Chunks im JSONL landen.", + "features.data.pii.body": "E-Mail, Telefon, Kreditkarte (Luhn-validiert), IBAN, nationale IDs (TR / DE / FR / US-SSN). Maskiert beim Ingest, bevor Chunks im JSONL landen (mit --pii-mask zu aktivieren).", "features.data.secrets.title":"Secrets-Bereinigung", "features.data.secrets.body": "AWS-Schlüssel, GitHub-PATs, Slack-/OpenAI-/Google-Tokens, JWTs, vollständige PEM-Privatschlüssel-Blöcke (BEGIN…END), Azure-Storage-Strings — geschwärzt mit [REDACTED-SECRET].", "features.data.dup.title": "Near-Duplicate-Erkennung", @@ -1272,7 +1272,7 @@ "features.data.multi.title": "Ingestion multi-format", "features.data.multi.body": "PDF, DOCX, EPUB, TXT, Markdown → JSONL prêt pour SFT. Splitter conscient du Markdown ; les tables DOCX deviennent des tables Markdown ; dédoublonnage des en-têtes/pieds PDF.", "features.data.pii.title": "Masquage des PII", - "features.data.pii.body": "E-mail, téléphone, carte de crédit (validée Luhn), IBAN, identifiants nationaux (TR / DE / FR / US-SSN). Masqués au moment de l'ingestion, avant que les chunks atterrissent dans le JSONL.", + "features.data.pii.body": "E-mail, téléphone, carte de crédit (validée Luhn), IBAN, identifiants nationaux (TR / DE / FR / US-SSN). Masqués au moment de l'ingestion, avant que les chunks atterrissent dans le JSONL (à activer avec --pii-mask).", "features.data.secrets.title":"Nettoyage des secrets", "features.data.secrets.body": "Clés AWS, PATs GitHub, jetons Slack / OpenAI / Google, JWTs, blocs PEM de clé privée complets (BEGIN…END), chaînes Azure storage — masqués par [REDACTED-SECRET].", "features.data.dup.title": "Détection de quasi-doublons", @@ -1351,7 +1351,7 @@ "features.data.multi.title": "Ingestión multi-formato", "features.data.multi.body": "PDF, DOCX, EPUB, TXT, Markdown → JSONL listo para SFT. Splitter consciente de Markdown; las tablas DOCX salen como tablas Markdown; dedup de cabecera/pie en PDF.", "features.data.pii.title": "Enmascaramiento PII", - "features.data.pii.body": "Email, teléfono, tarjeta de crédito (validada con Luhn), IBAN, IDs nacionales (TR / DE / FR / US-SSN). Enmascarados en el momento de la ingestión, antes de que los chunks lleguen al JSONL.", + "features.data.pii.body": "Email, teléfono, tarjeta de crédito (validada con Luhn), IBAN, IDs nacionales (TR / DE / FR / US-SSN). Enmascarados en el momento de la ingestión, antes de que los chunks lleguen al JSONL (se activa con --pii-mask).", "features.data.secrets.title":"Limpieza de secretos", "features.data.secrets.body": "Claves AWS, PATs de GitHub, tokens de Slack / OpenAI / Google, JWTs, bloques PEM de clave privada completos (BEGIN…END), strings de Azure storage — redactados con [REDACTED-SECRET].", "features.data.dup.title": "Detección de cuasi-duplicados", @@ -1430,7 +1430,7 @@ "features.data.multi.title": "多格式 ingest", "features.data.multi.body": "PDF、DOCX、EPUB、TXT、Markdown → SFT 就绪的 JSONL。Markdown 感知 splitter;DOCX 表格输出为 Markdown 表格;PDF 页眉/页脚去重。", "features.data.pii.title": "PII 屏蔽", - "features.data.pii.body": "邮箱、电话、信用卡 (Luhn 校验)、IBAN、国家身份证 (TR / DE / FR / US-SSN)。在 ingest 时,chunk 进入 JSONL 之前完成屏蔽。", + "features.data.pii.body": "邮箱、电话、信用卡 (Luhn 校验)、IBAN、国家身份证 (TR / DE / FR / US-SSN)。在 ingest 时,chunk 进入 JSONL 之前完成屏蔽(需通过 --pii-mask 开启)。", "features.data.secrets.title":"Secrets 清洗", "features.data.secrets.body": "AWS 密钥、GitHub PATs、Slack / OpenAI / Google token、JWT、完整 PEM 私钥块 (BEGIN…END)、Azure storage 字符串 — 用 [REDACTED-SECRET] 替换。", "features.data.dup.title": "近重复检测", @@ -1810,16 +1810,16 @@ "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", "wizard.synthetic.api_key_env.label": "Teacher API key env var", "wizard.synthetic.api_key_env.hint": "Environment variable name carrying the teacher API key. Empty = local teacher model.", - "wizard.retention.title": "Retention horizons (GDPR Art. 5+17)", - "wizard.retention.hint": "How long ForgeLM keeps audit logs, staging models, and ephemeral artefacts.", + "wizard.retention.title": "Retention horizons (GDPR Art. 5(1)(e))", + "wizard.retention.hint": "Ages at which forgelm purge --check-policy reports artefacts as overdue; nothing is deleted. Set a value to 0 to stop reporting that artefact. The retention block is written to the YAML only when a value differs from its default; without it, --check-policy checks nothing.", "wizard.retention.audit_log.label": "Audit log retention (days)", - "wizard.retention.audit_log.hint": "How long to keep audit_log.jsonl. Default 1825 (5 years) matches EU AI Act guidance.", + "wizard.retention.audit_log.hint": "How long before forgelm purge --check-policy reports audit_log.jsonl as overdue. Default 1825 (5 years) is a ForgeLM choice, not a legal period.", "wizard.retention.staging.label": "Staging horizon (days)", - "wizard.retention.staging.hint": "How long to keep rejected models in final_model.staging/. Default 7.", + "wizard.retention.staging.hint": "Age at which forgelm purge --check-policy reports a final_model.staging.*/ (or legacy final_model.staging/) directory as overdue; nothing is deleted automatically. Default 7.", "wizard.retention.ephemeral.label": "Ephemeral artefacts (days)", - "wizard.retention.ephemeral.hint": "How long to keep intermediate checkpoints, eval samples. Default 90.", + "wizard.retention.ephemeral.hint": "Days after the audit log's first event (file age if there is no log) at which forgelm purge --check-policy reports compliance/ and data_audit_report.json as overdue. Default 90.", "wizard.retention.raw_documents.label": "Raw documents (days)", - "wizard.retention.raw_documents.hint": "How long to keep raw PDFs / DOCX after ingestion. Default 90.", + "wizard.retention.raw_documents.hint": "Days after the audit log's first event (file age if there is no log) at which forgelm purge --check-policy reports raw_documents/ or ingestion_output/ as overdue. Default 90.", "wizard.retention.enforce.label": "Policy mode (reserved)", "wizard.retention.enforce.hint": "Reserved: no ForgeLM command acts on this yet, so all three modes behave the same. Retention horizons are only reported by forgelm purge --check-policy; nothing is deleted automatically.", "wizard.retention.enforce.log_only": "log_only — default (no command acts on it)", @@ -2208,16 +2208,16 @@ "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", "wizard.synthetic.api_key_env.label": "Teacher API key env değişkeni", "wizard.synthetic.api_key_env.hint": "Teacher API anahtarını taşıyan ortam değişkeni adı. Boş = yerel teacher modeli.", - "wizard.retention.title": "Saklama ufukları (GDPR Madde 5+17)", - "wizard.retention.hint": "ForgeLM audit log, staging model ve geçici artefact'ları ne kadar tutar.", + "wizard.retention.title": "Saklama ufukları (GDPR Madde 5(1)(e))", + "wizard.retention.hint": "forgelm purge --check-policy'nin artefact'ları süresi geçmiş olarak raporladığı yaşlar; hiçbir şey silinmez. Bir değeri 0 yapmak o artefact'ın raporlanmasını durdurur. retention bloğu YAML'a yalnızca bir değer varsayılandan farklıysa yazılır; blok yoksa --check-policy hiçbir şeyi kontrol etmez.", "wizard.retention.audit_log.label": "Audit log saklama (gün)", - "wizard.retention.audit_log.hint": "audit_log.jsonl ne kadar tutulsun. Varsayılan 1825 (5 yıl) EU AI Act rehberiyle uyumlu.", + "wizard.retention.audit_log.hint": "forgelm purge --check-policy audit_log.jsonl'i kaç gün sonra süresi geçmiş olarak raporlasın. Varsayılan 1825 (5 yıl) bir ForgeLM tercihidir, yasal süre değildir.", "wizard.retention.staging.label": "Staging ufku (gün)", - "wizard.retention.staging.hint": "final_model.staging/ içinde reddedilen modeller ne kadar tutulsun. Varsayılan 7.", + "wizard.retention.staging.hint": "forgelm purge --check-policy bir final_model.staging.*/ (veya eski final_model.staging/) dizinini hangi yaşta süresi geçmiş olarak raporlasın; hiçbir şey otomatik silinmez. Varsayılan 7.", "wizard.retention.ephemeral.label": "Geçici artefact'lar (gün)", - "wizard.retention.ephemeral.hint": "Ara checkpoint'ler, eval örnekleri ne kadar tutulsun. Varsayılan 90.", + "wizard.retention.ephemeral.hint": "forgelm purge --check-policy, audit log'un ilk olayından itibaren (audit log yoksa dosya yaşı) kaç gün sonra compliance/ ve data_audit_report.json'u süresi geçmiş olarak raporlasın. Varsayılan 90.", "wizard.retention.raw_documents.label": "Ham dokümanlar (gün)", - "wizard.retention.raw_documents.hint": "Ingestion sonrası ham PDF / DOCX ne kadar tutulsun. Varsayılan 90.", + "wizard.retention.raw_documents.hint": "forgelm purge --check-policy, audit log'un ilk olayından itibaren (audit log yoksa dosya yaşı) kaç gün sonra raw_documents/ veya ingestion_output/ dizinini süresi geçmiş olarak raporlasın. Varsayılan 90.", "wizard.retention.enforce.label": "Politika modu (ayrılmış)", "wizard.retention.enforce.hint": "Ayrılmış: hiçbir ForgeLM komutu bunu henüz kullanmaz, bu yüzden üç mod da aynı davranır. Saklama ufukları yalnızca forgelm purge --check-policy ile raporlanır; hiçbir şey otomatik silinmez.", "wizard.retention.enforce.log_only": "log_only — varsayılan (hiçbir komut kullanmaz)", @@ -2602,16 +2602,16 @@ "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", "wizard.synthetic.api_key_env.label": "Teacher API-Key Env-Variable", "wizard.synthetic.api_key_env.hint": "Env-Variable mit dem Teacher-API-Key. Leer = lokales Teacher-Modell.", - "wizard.retention.title": "Aufbewahrungsfristen (DSGVO Art. 5+17)", - "wizard.retention.hint": "Wie lange ForgeLM Audit-Logs, Staging-Modelle, ephemere Artefakte aufbewahrt.", + "wizard.retention.title": "Aufbewahrungshorizonte (DSGVO Art. 5 Abs. 1 Buchst. e)", + "wizard.retention.hint": "Alter, ab dem forgelm purge --check-policy Artefakte als überfällig meldet; nichts wird gelöscht. Ein Wert von 0 beendet die Meldung für dieses Artefakt. Der retention-Block wird nur dann in die YAML geschrieben, wenn ein Wert vom Default abweicht; ohne ihn prüft --check-policy nichts.", "wizard.retention.audit_log.label": "Audit-Log Aufbewahrung (Tage)", - "wizard.retention.audit_log.hint": "Wie lange audit_log.jsonl behalten. Default 1825 (5 Jahre) entspricht EU AI Act-Vorgabe.", + "wizard.retention.audit_log.hint": "Nach wie vielen Tagen forgelm purge --check-policy audit_log.jsonl als überfällig meldet. Der Default 1825 (5 Jahre) ist eine Vorgabe von ForgeLM, keine gesetzliche Frist.", "wizard.retention.staging.label": "Staging-Horizont (Tage)", - "wizard.retention.staging.hint": "Wie lange abgelehnte Modelle in final_model.staging/ behalten. Default 7.", + "wizard.retention.staging.hint": "Ab welchem Alter forgelm purge --check-policy ein final_model.staging.*/-Verzeichnis (oder das frühere final_model.staging/ im Legacy-Layout) als überfällig meldet; nichts wird automatisch gelöscht. Default 7.", "wizard.retention.ephemeral.label": "Ephemere Artefakte (Tage)", - "wizard.retention.ephemeral.hint": "Wie lange Zwischen-Checkpoints, Eval-Samples behalten. Default 90.", + "wizard.retention.ephemeral.hint": "Nach wie vielen Tagen seit dem ersten Ereignis im Audit-Log (ohne Audit-Log: Dateialter) forgelm purge --check-policy compliance/ und data_audit_report.json als überfällig meldet. Default 90.", "wizard.retention.raw_documents.label": "Rohdokumente (Tage)", - "wizard.retention.raw_documents.hint": "Wie lange rohe PDFs/DOCX nach Ingestion behalten. Default 90.", + "wizard.retention.raw_documents.hint": "Nach wie vielen Tagen seit dem ersten Ereignis im Audit-Log (ohne Audit-Log: Dateialter) forgelm purge --check-policy raw_documents/ oder ingestion_output/ als überfällig meldet. Default 90.", "wizard.retention.enforce.label": "Richtlinienmodus (reserviert)", "wizard.retention.enforce.hint": "Reserviert: Noch wertet kein ForgeLM-Befehl dies aus, daher verhalten sich alle drei Modi gleich. Aufbewahrungshorizonte meldet nur forgelm purge --check-policy; nichts wird automatisch gelöscht.", "wizard.retention.enforce.log_only": "log_only — Default (kein Befehl wertet es aus)", @@ -2996,16 +2996,16 @@ "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", "wizard.synthetic.api_key_env.label": "Variable env clé API teacher", "wizard.synthetic.api_key_env.hint": "Nom variable env portant clé API teacher. Vide = modèle teacher local.", - "wizard.retention.title": "Horizons de rétention (RGPD Art. 5+17)", - "wizard.retention.hint": "Combien de temps ForgeLM garde audit logs, modèles staging, artefacts éphémères.", + "wizard.retention.title": "Horizons de rétention (RGPD art. 5, paragraphe 1, point e)", + "wizard.retention.hint": "Seuils d'âge à partir desquels forgelm purge --check-policy signale des artefacts comme ayant dépassé leur durée de conservation ; rien n'est supprimé. Une valeur de 0 arrête le signalement de cet artefact. Le bloc retention n'est écrit dans le YAML que si une valeur diffère de sa valeur par défaut ; sans lui, --check-policy ne vérifie rien.", "wizard.retention.audit_log.label": "Rétention audit log (jours)", - "wizard.retention.audit_log.hint": "Combien de temps garder audit_log.jsonl. Default 1825 (5 ans) match EU AI Act.", + "wizard.retention.audit_log.hint": "Nombre de jours au-delà duquel forgelm purge --check-policy signale audit_log.jsonl comme ayant dépassé sa durée de conservation. Par défaut : 1825 (5 ans), un choix de ForgeLM et non un délai légal.", "wizard.retention.staging.label": "Horizon staging (jours)", - "wizard.retention.staging.hint": "Combien de temps garder modèles rejetés dans final_model.staging/. Default 7.", + "wizard.retention.staging.hint": "Âge à partir duquel forgelm purge --check-policy signale un répertoire final_model.staging.*/ (ou l'ancien format final_model.staging/) comme ayant dépassé sa durée de conservation ; rien n'est supprimé automatiquement. Par défaut : 7.", "wizard.retention.ephemeral.label": "Artefacts éphémères (jours)", - "wizard.retention.ephemeral.hint": "Combien de temps garder checkpoints intermédiaires, samples eval. Default 90.", + "wizard.retention.ephemeral.hint": "Nombre de jours, comptés depuis le premier événement du journal d'audit (à défaut de journal : l'âge du fichier), au-delà duquel forgelm purge --check-policy signale compliance/ et data_audit_report.json comme ayant dépassé leur durée de conservation. Par défaut : 90.", "wizard.retention.raw_documents.label": "Documents bruts (jours)", - "wizard.retention.raw_documents.hint": "Combien de temps garder PDFs/DOCX bruts après ingestion. Default 90.", + "wizard.retention.raw_documents.hint": "Nombre de jours, comptés depuis le premier événement du journal d'audit (à défaut de journal : l'âge du fichier), au-delà duquel forgelm purge --check-policy signale raw_documents/ ou ingestion_output/ comme ayant dépassé leur durée de conservation. Par défaut : 90.", "wizard.retention.enforce.label": "Mode de politique (réservé)", "wizard.retention.enforce.hint": "Réservé : aucune commande ForgeLM n'agit encore dessus, donc les trois modes se comportent de la même façon. Les horizons de rétention ne sont signalés que par forgelm purge --check-policy ; rien n'est supprimé automatiquement.", "wizard.retention.enforce.log_only": "log_only — défaut (aucune commande n'en tient compte)", @@ -3390,16 +3390,16 @@ "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", "wizard.synthetic.api_key_env.label": "Variable env clave API teacher", "wizard.synthetic.api_key_env.hint": "Nombre variable env con clave API teacher. Vacío = modelo teacher local.", - "wizard.retention.title": "Horizontes de retención (RGPD Art. 5+17)", - "wizard.retention.hint": "Cuánto tiempo ForgeLM guarda audit logs, modelos staging, artefactos efímeros.", + "wizard.retention.title": "Horizontes de retención (RGPD art. 5, apartado 1, letra e)", + "wizard.retention.hint": "Antigüedad a partir de la cual forgelm purge --check-policy señala los artefactos como vencidos; no se elimina nada. Con un valor de 0, ese artefacto deja de señalarse. El bloque retention solo se escribe en el YAML si algún valor difiere de su valor por defecto; sin él, --check-policy no comprueba nada.", "wizard.retention.audit_log.label": "Retención audit log (días)", - "wizard.retention.audit_log.hint": "Cuánto guardar audit_log.jsonl. Default 1825 (5 años) coincide con EU AI Act.", + "wizard.retention.audit_log.hint": "Días tras los cuales forgelm purge --check-policy señala audit_log.jsonl como vencido. El valor por defecto, 1825 (5 años), es una elección de ForgeLM, no un plazo legal.", "wizard.retention.staging.label": "Horizonte staging (días)", - "wizard.retention.staging.hint": "Cuánto guardar modelos rechazados en final_model.staging/. Default 7.", + "wizard.retention.staging.hint": "Antigüedad a partir de la cual forgelm purge --check-policy señala como vencido un directorio final_model.staging.*/ (o el formato heredado final_model.staging/); no se elimina nada automáticamente. Valor por defecto: 7.", "wizard.retention.ephemeral.label": "Artefactos efímeros (días)", - "wizard.retention.ephemeral.hint": "Cuánto guardar checkpoints intermedios, samples eval. Default 90.", + "wizard.retention.ephemeral.hint": "Días desde el primer evento del log de auditoría (si no hay log, la antigüedad del archivo) tras los cuales forgelm purge --check-policy señala compliance/ y data_audit_report.json como vencidos. Valor por defecto: 90.", "wizard.retention.raw_documents.label": "Documentos crudos (días)", - "wizard.retention.raw_documents.hint": "Cuánto guardar PDFs/DOCX crudos tras ingestion. Default 90.", + "wizard.retention.raw_documents.hint": "Días desde el primer evento del log de auditoría (si no hay log, la antigüedad del archivo) tras los cuales forgelm purge --check-policy señala raw_documents/ o ingestion_output/ como vencidos. Valor por defecto: 90.", "wizard.retention.enforce.label": "Modo de política (reservado)", "wizard.retention.enforce.hint": "Reservado: ningún comando de ForgeLM actúa aún sobre esto, así que los tres modos se comportan igual. Los horizontes de retención solo los informa forgelm purge --check-policy; nada se elimina automáticamente.", "wizard.retention.enforce.log_only": "log_only — default (ningún comando lo usa)", @@ -3784,16 +3784,16 @@ "wizard.synthetic.seed_file.placeholder": "./data/seed_prompts.jsonl", "wizard.synthetic.api_key_env.label": "Teacher API 密钥环境变量", "wizard.synthetic.api_key_env.hint": "承载 teacher API 密钥的环境变量名。空 = 本地 teacher 模型。", - "wizard.retention.title": "保留期限(GDPR 第 5+17 条)", - "wizard.retention.hint": "ForgeLM 保留 audit log、staging 模型、临时 artefact 多长时间。", + "wizard.retention.title": "保留期限(GDPR 第 5 条第 1 款第 (e) 项)", + "wizard.retention.hint": "forgelm purge --check-policy 将 artefact 报告为已超期的期限(天数);不会删除任何内容。将某一项设为 0 即不再报告该 artefact。仅当某个值与默认值不同时,retention 块才会写入 YAML;没有该块时,--check-policy 不检查任何内容。", "wizard.retention.audit_log.label": "Audit log 保留(天)", - "wizard.retention.audit_log.hint": "audit_log.jsonl 保留多久。默认 1825(5 年)符合 EU AI Act。", + "wizard.retention.audit_log.hint": "forgelm purge --check-policy 在多少天后将 audit_log.jsonl 报告为已超期。默认 1825(5 年)是 ForgeLM 的选择,不是法定期限。", "wizard.retention.staging.label": "Staging 期限(天)", - "wizard.retention.staging.hint": "final_model.staging/ 中被拒绝的模型保留多久。默认 7。", + "wizard.retention.staging.hint": "forgelm purge --check-policy 在 final_model.staging.*/(或旧版 final_model.staging/)目录满多少天时将其报告为已超期;不会自动删除任何内容。默认 7。", "wizard.retention.ephemeral.label": "临时 artefact(天)", - "wizard.retention.ephemeral.hint": "中间 checkpoint、eval 样本保留多久。默认 90。", + "wizard.retention.ephemeral.hint": "自审计日志第一条事件起(无审计日志时按文件修改时间)满多少天后,forgelm purge --check-policy 会将 compliance/ 和 data_audit_report.json 报告为已超期。默认 90。", "wizard.retention.raw_documents.label": "原始文档(天)", - "wizard.retention.raw_documents.hint": "ingestion 后原始 PDF/DOCX 保留多久。默认 90。", + "wizard.retention.raw_documents.hint": "自审计日志第一条事件起(无审计日志时按文件修改时间)满多少天后,forgelm purge --check-policy 会将 raw_documents/ 或 ingestion_output/ 报告为已超期。默认 90。", "wizard.retention.enforce.label": "策略模式(保留)", "wizard.retention.enforce.hint": "保留项:目前没有任何 ForgeLM 命令使用它,因此三种模式行为相同。保留期限仅由 forgelm purge --check-policy 报告;不会自动删除任何内容。", "wizard.retention.enforce.log_only": "log_only — 默认(没有命令使用它)", @@ -3854,7 +3854,7 @@ "compliance.art9.title": "Risk management", "compliance.art9.body": "A continuous, iterative process: identify foreseeable risks, evaluate, mitigate. Auto-revert on threshold breach is the technical mitigation; trend tracking is the monitoring loop.", "compliance.art10.title": "Data & data governance", - "compliance.art10.body": "Quality, relevance, representativeness criteria. Bias detection, PII redaction, cross-split leakage gate. Optional Presidio ML-NER PII layer and Croissant 1.0 dataset card emitted alongside the audit. Every audit report becomes an Article 10 evidence document.", + "compliance.art10.body": "Quality, relevance, representativeness criteria you declare. PII counts and masking, cross-split leakage report. Optional Presidio ML-NER PII layer and Croissant 1.0 dataset card emitted alongside the audit. Every audit report can be filed as part of your Article 10 evidence; collection, provenance and bias-examination documentation still come from you.", "compliance.art11.title": "Technical documentation", "compliance.art11.body": "Annex IV's nine required §1-9 sections — auto-populated from the run: system identification, intended purpose, components, computational resources, data governance, technical documentation, monitoring & logging, performance metrics, risk management.", "compliance.art12.title": "Record-keeping (logs)", @@ -3870,9 +3870,9 @@ "compliance.gdpr.title": "PII redaction at ingest", "compliance.gdpr.body": "--pii-mask redacts emails, phones, credit cards (Luhn), IBAN, national IDs (TR/DE/FR/US-SSN) before chunks land in JSONL. It reduces what reaches training and supports data minimisation (Article 5(1)(c)); names and free text are not detected, so it is an aid, not a guarantee.", "compliance.gdpr15.title": "Right of access", - "compliance.gdpr15.body": "forgelm reverse-pii scans masked JSONL corpora for plaintext residuals or salted SHA-256 digests of a data-subject identifier and reports every row of every split where that subject appears. It finds only what is literally in the files you scan: raw sources and the trained model are not searched, and handling an access request stays your process.", + "compliance.gdpr15.body": "forgelm reverse-pii searches JSONL corpora for a data-subject identifier, as plain text or as a salted SHA-256 digest written by an external masking pipeline, and reports every line in the scanned files that contains it. By default the match is literal and case-sensitive (--type custom takes a regex). It is an input to your Article 15 response, which must also give a copy of the data and the Article 15(1)(a)-(h) information; raw sources and the trained model are not searched.", "compliance.gdpr17.title": "Right to erasure", - "compliance.gdpr17.body": "forgelm purge deletes a data-subject's rows from training corpora and stamps data.erasure_requested + data.erasure_completed events into the append-only audit log so post-deletion runs are forensically linked to the erasure event.", + "compliance.gdpr17.body": "forgelm purge deletes rows by id from a training corpus and records data.erasure_requested + data.erasure_completed events in the append-only audit log. It is one step of an Article 17 erasure: trained weights, source documents and copies elsewhere are not changed.", "compliance.iso.eyebrow": "ISO 27001 · SOC 2 Type II", "compliance.iso.title": "Aligned, not certified", "compliance.iso.lead": "Software cannot be ISO 27001 or SOC 2 certified — only organisations can. ForgeLM is aligned with the controls an auditor asks the deployer about: it produces the audit-trail, change-management, data-lineage, and supply-chain evidence on every run, ready to drop into your audit binder.", @@ -3913,7 +3913,7 @@ "compliance.art9.title": "Risk yönetimi", "compliance.art9.body": "Sürekli, iteratif süreç: öngörülebilir riskleri tanımla, değerlendir, azalt. Eşik aşımında otomatik artefakt silme teknik mitigasyondur; izleme döngüsünü trend izleme sağlar.", "compliance.art10.title": "Veri ve veri yönetimi", - "compliance.art10.body": "Kalite, uygunluk, temsiliyet kriterleri. Bias tespiti, PII redaksiyonu, split-arası sızıntı kapısı. Opsiyonel Presidio ML-NER PII katmanı ve audit ile birlikte yayınlanan Croissant 1.0 dataset card. Her audit raporu Madde 10 kanıt belgesi olur.", + "compliance.art10.body": "Beyan ettiğiniz kalite, uygunluk, temsiliyet kriterleri. PII sayıları ve maskeleme, split-arası sızıntı raporu. Opsiyonel Presidio ML-NER PII katmanı ve audit ile birlikte yayınlanan Croissant 1.0 dataset card. Her audit raporu Madde 10 kanıtınızın bir parçası olarak sunulabilir; toplama, köken ve önyargı-inceleme dokümantasyonu yine sizden gelir.", "compliance.art11.title": "Teknik dokümantasyon", "compliance.art11.body": "Annex IV'ün §1-9 dokuz zorunlu bölümü — koşudan otomatik doldurulur: sistem kimliği, amaçlanan kullanım, bileşenler, hesaplama kaynakları, veri yönetişimi, teknik dokümantasyon, izleme ve loglama, performans metrikleri, risk yönetimi.", "compliance.art12.title": "Kayıt tutma (loglar)", @@ -3929,9 +3929,9 @@ "compliance.gdpr.title": "Ingest'te PII redaksiyonu", "compliance.gdpr.body": "--pii-mask chunk'lar JSONL'a düşmeden e-posta, telefon, kredi kartı (Luhn), IBAN, ulusal kimlik (TR/DE/FR/US-SSN) maskeler. Eğitime ulaşanı azaltır ve veri minimizasyonunu (Madde 5(1)(c)) destekler; isimler ve serbest metin tespit edilmez, bu yüzden bir yardımcıdır, garanti değildir.", "compliance.gdpr15.title": "Erişim hakkı", - "compliance.gdpr15.body": "forgelm reverse-pii maskelenmiş JSONL corpus'larında bir veri sahibi tanımlayıcısının düz-metin kalıntılarını veya tuzlanmış SHA-256 digest'lerini arar ve o öznenin geçtiği her split'in her satırını raporlar. Yalnızca taradığınız dosyalarda harfiyen bulunanı bulur: ham kaynaklar ve eğitilmiş model aranmaz; bir erişim talebini yürütmek sizin sürecinizde kalır.", + "compliance.gdpr15.body": "forgelm reverse-pii JSONL corpus'larında bir veri sahibi tanımlayıcısının düz metin olarak geçtiği yerleri veya harici bir maskeleme pipeline'ının yazdığı salt'lı SHA-256 digest'lerini arar ve taradığınız dosyalarda onu içeren her satırı raporlar. Varsayılan olarak eşleşme birebir ve büyük/küçük harfe duyarlıdır (--type custom bir regex alır). Madde 15 yanıtınızın bir girdisidir; yanıt ayrıca verinin bir kopyasını ve Madde 15(1)(a)-(h) bilgilerini içermelidir. Ham kaynaklar ve eğitilmiş model aranmaz.", "compliance.gdpr17.title": "Silme hakkı", - "compliance.gdpr17.body": "forgelm purge bir veri sahibinin satırlarını eğitim corpus'undan siler ve append-only audit log'a data.erasure_requested + data.erasure_completed olaylarını damgalar; silme sonrası çalışmalar adli olarak silme olayına bağlanır.", + "compliance.gdpr17.body": "forgelm purge bir eğitim corpus'undan satırları id ile siler ve append-only audit log'a data.erasure_requested + data.erasure_completed olaylarını kaydeder. Madde 17 kapsamındaki silmenin bir adımıdır: eğitilmiş ağırlıklar, kaynak dokümanlar ve başka yerlerdeki kopyalar değişmez.", "compliance.iso.eyebrow": "ISO 27001 · SOC 2 Type II", "compliance.iso.title": "Hizalanmış, sertifikalı değil", "compliance.iso.lead": "Yazılım ISO 27001 veya SOC 2 sertifikalı olamaz — sadece organizasyonlar olabilir. ForgeLM, denetçinin deployer'a sorduğu kontrollerle hizalıdır: her çalışmada audit izi, change-management, veri kökenleri ve tedarik zinciri kanıtlarını üretir; doğrudan audit klasörünüze düşer.", @@ -3972,7 +3972,7 @@ "compliance.art9.title": "Risikomanagement", "compliance.art9.body": "Ein kontinuierlicher, iterativer Prozess: vorhersehbare Risiken identifizieren, bewerten, mindern. Das automatische Verwerfen der Artefakte bei Schwellenüberschreitung ist die technische Maßnahme; Trend-Tracking ist die Monitoring-Schleife.", "compliance.art10.title": "Daten & Datengovernance", - "compliance.art10.body": "Kriterien für Qualität, Relevanz, Repräsentativität. Bias-Erkennung, PII-Schwärzung, Split-übergreifender Leak-Check. Optionale Presidio-ML-NER-PII-Schicht und Croissant-1.0-Datensatzkarte, die zusammen mit dem Audit ausgegeben wird. Jeder Audit-Bericht wird zu einem Artikel-10-Beweisdokument.", + "compliance.art10.body": "Von Ihnen deklarierte Kriterien für Qualität, Relevanz, Repräsentativität. PII-Zählungen und -Schwärzung, Bericht über Split-übergreifende Leaks. Optionale Presidio-ML-NER-PII-Schicht und Croissant-1.0-Datensatzkarte, die zusammen mit dem Audit ausgegeben wird. Jeder Audit-Bericht kann als Teil Ihrer Artikel-10-Nachweise abgelegt werden; Erhebungs-, Herkunfts- und Bias-Prüfungsdokumentation liefern weiterhin Sie.", "compliance.art11.title": "Technische Dokumentation", "compliance.art11.body": "Die neun Pflichtabschnitte §1-9 von Annex IV — automatisch aus dem Lauf befüllt: Systemidentifikation, Zweckbestimmung, Komponenten, Rechenressourcen, Daten-Governance, technische Dokumentation, Monitoring & Logging, Leistungskennzahlen, Risikomanagement.", "compliance.art12.title": "Aufzeichnungen (Logs)", @@ -4015,7 +4015,7 @@ "compliance.art9.title": "Gestion des risques", "compliance.art9.body": "Un processus continu et itératif : identifier les risques prévisibles, évaluer, atténuer. La suppression automatique des artefacts en cas de dépassement de seuil est la mitigation technique ; le suivi de tendance est la boucle de surveillance.", "compliance.art10.title": "Données & gouvernance des données", - "compliance.art10.body": "Critères de qualité, pertinence, représentativité. Détection de biais, masquage des PII, gate de fuite inter-split. Couche PII Presidio ML-NER optionnelle et carte de jeu de données Croissant 1.0 émise aux côtés de l'audit. Chaque rapport d'audit devient un document de preuve pour l'Article 10.", + "compliance.art10.body": "Critères de qualité, pertinence, représentativité que vous déclarez. Comptages de PII et masquage, rapport de fuite inter-split. Couche PII Presidio ML-NER optionnelle et carte de jeu de données Croissant 1.0 émise aux côtés de l'audit. Chaque rapport d'audit peut être versé à vos preuves de l'Article 10 ; la documentation sur la collecte, la provenance et l'examen des biais reste de votre côté.", "compliance.art11.title": "Documentation technique", "compliance.art11.body": "Les neuf sections obligatoires §1-9 d'Annex IV — auto-remplies à partir de l'exécution : identification du système, finalité prévue, composants, ressources de calcul, gouvernance des données, documentation technique, surveillance & journalisation, métriques de performance, gestion des risques.", "compliance.art12.title": "Tenue de registres (logs)", @@ -4058,7 +4058,7 @@ "compliance.art9.title": "Gestión de riesgos", "compliance.art9.body": "Un proceso continuo e iterativo: identifique riesgos previsibles, evalúe, mitigue. El descarte automático de artefactos ante violación de umbral es la mitigación técnica; el seguimiento de tendencias es el bucle de monitorización.", "compliance.art10.title": "Datos y gobernanza de datos", - "compliance.art10.body": "Criterios de calidad, relevancia, representatividad. Detección de sesgos, redacción de PII, puerta de fuga entre splits. Capa PII de Presidio ML-NER opcional y tarjeta de dataset Croissant 1.0 emitida junto con la auditoría. Cada informe de auditoría se convierte en un documento probatorio del Artículo 10.", + "compliance.art10.body": "Criterios de calidad, relevancia y representatividad que usted declara. Recuentos de PII y enmascaramiento, informe de fuga entre splits. Capa PII de Presidio ML-NER opcional y tarjeta de dataset Croissant 1.0 emitida junto con la auditoría. Cada informe de auditoría puede archivarse como parte de sus pruebas del Artículo 10; la documentación de recopilación, procedencia y examen de sesgos sigue siendo suya.", "compliance.art11.title": "Documentación técnica", "compliance.art11.body": "Las nueve secciones obligatorias §1-9 del Annex IV — auto-rellenadas desde la ejecución: identificación del sistema, propósito previsto, componentes, recursos computacionales, gobernanza de datos, documentación técnica, monitorización y registro, métricas de rendimiento, gestión de riesgos.", "compliance.art12.title": "Registro (logs)", @@ -4101,7 +4101,7 @@ "compliance.art9.title": "风险管理", "compliance.art9.body": "一个持续的、迭代的过程:识别可预见的风险、评估、缓解。阈值越界时自动丢弃训练产物是技术缓解;趋势跟踪是监控回路。", "compliance.art10.title": "数据 & 数据治理", - "compliance.art10.body": "质量、相关性、代表性标准。偏见检测、PII 屏蔽、split 间泄漏 gate。可选的 Presidio ML-NER PII 层,以及与审计一同输出的 Croissant 1.0 数据集卡片。每份审计报告都成为 Article 10 的证据文档。", + "compliance.art10.body": "你声明的质量、相关性、代表性标准。PII 计数与屏蔽、split 间泄漏报告。可选的 Presidio ML-NER PII 层,以及与审计一同输出的 Croissant 1.0 数据集卡片。每份审计报告都可以作为你的 Article 10 证据的一部分归档;数据收集、来源和偏见审查文档仍由你提供。", "compliance.art11.title": "技术文档", "compliance.art11.body": "Annex IV §1-9 的九个必需章节 — 由运行自动填充:系统标识、预期目的、组件、计算资源、数据治理、技术文档、监控与日志、性能指标、风险管理。", "compliance.art12.title": "记录保存 (日志)", @@ -4344,7 +4344,7 @@ "privacy.header.eyebrow": "Privacy", "privacy.header.title": "Privacy policy", "privacy.header.lead": "ForgeLM is open-source software you run on your own infrastructure. This page covers what this website does — not the toolkit itself.", - "privacy.body": "

Short version

This site is static HTML. We do not run analytics, tracking pixels, advertising trackers, A/B tests, fingerprinting, or any third-party telemetry. Three third-party requests can happen here: Google Fonts is fetched on every page load (the Inter and JetBrains Mono typefaces); Mermaid is loaded from jsDelivr on the Guide page only (to render diagrams); and Formspree only when you submit the contact form. All three are detailed below; nothing else.

What we collect

From normal browsing

Nothing. No cookies are set. Two values are stored in localStorage on your device:

  • forgelm-lang — your chosen interface language (en, tr, de, fr, es, zh).
  • forgelm-theme — your chosen colour theme (dark or light).

Both stay on your device and are never transmitted. Clearing your browser storage removes them.

From the contact form

When you submit the form on the Contact page, the values you typed (name, email, organisation, topic, subject, message) are sent to Formspree, which forwards them to the project maintainer. Formspree's privacy practices are governed by their own policy at formspree.io/legal/privacy-policy.

Your email is used only to reply to your message. We do not add it to a mailing list, do not share it with third parties, and do not retain it after the conversation is resolved.

Hosting

This site is served as static files. Your browser will exchange standard request headers (User-Agent, Accept-Language, IP address) with whichever CDN / web server hosts the files; that exchange is governed by the hosting provider's privacy practices and is not under ForgeLM's control. We do not log this traffic ourselves.

Third-party assets

The site loads:

  • Google Fonts — the Inter and JetBrains Mono typefaces. Google may log requests as described in their privacy policy.
  • jsDelivr — Mermaid 10.9.4 is loaded on the Guide page only to render the architecture / flow diagrams. jsDelivr may log requests as described in their privacy policy.
  • Formspree — only when you submit the contact form, as described above.

Nothing else is loaded from a third party. There is no Google Analytics, no Facebook pixel, no Hotjar, no Mixpanel, no whatever-the-current-tracker is.

Children

This site is aimed at developers, ML engineers, and compliance professionals. It is not directed at children under 16.

Your rights (GDPR / KVKK)

If you believe we hold personal data about you and you want it accessed, corrected, or deleted, write to us via the contact form (or GitHub Issues) and we will respond within 30 days. Because the only personal data we hold is what you sent in the contact form, deletion is straightforward: we delete the email thread.

The toolkit itself

The ForgeLM Python package — installed via pip install forgelm — does not phone home. It does not collect telemetry, does not send usage data anywhere, and works offline (air-gap-capable). Models, datasets, and tokenizers you reference in your YAML config are downloaded only at your explicit request and only from sources you specify.

Data-subject rights you can ship in your own pipeline

If your organisation deploys ForgeLM, the toolkit gives you concrete primitives for the data-subject rights side of GDPR: forgelm reverse-pii scans the corpus for plaintext residuals or salted-digest matches of a subject identifier and reports every row where they appear (Article 15 access), and forgelm purge deletes those rows from your training corpora and stamps the deletion in the append-only audit log (Article 17 erasure). On top of that, ForgeLM is aligned with ISO 27001 / SOC 2 Type II controls — see the Compliance page for the full evidence map.

Changes

If we update this page, the date at the top changes. Material changes are noted in the project's CHANGELOG.md.

Questions

Open the contact form or a GitHub Discussion.

" + "privacy.body": "

Short version

This site is static HTML. We do not run analytics, tracking pixels, advertising trackers, A/B tests, fingerprinting, or any third-party telemetry. Three third-party requests can happen here: Google Fonts is fetched on every page load (the Inter and JetBrains Mono typefaces); Mermaid is loaded from jsDelivr on the Guide page only (to render diagrams); and Formspree only when you submit the contact form. All three are detailed below; nothing else.

What we collect

From normal browsing

Nothing. No cookies are set. Two values are stored in localStorage on your device:

  • forgelm-lang — your chosen interface language (en, tr, de, fr, es, zh).
  • forgelm-theme — your chosen colour theme (dark or light).

Both stay on your device and are never transmitted. Clearing your browser storage removes them.

From the contact form

When you submit the form on the Contact page, the values you typed (name, email, organisation, topic, subject, message) are sent to Formspree, which forwards them to the project maintainer. Formspree's privacy practices are governed by their own policy at formspree.io/legal/privacy-policy.

Your email is used only to reply to your message. We do not add it to a mailing list, do not share it with third parties, and do not retain it after the conversation is resolved.

Hosting

This site is served as static files. Your browser will exchange standard request headers (User-Agent, Accept-Language, IP address) with whichever CDN / web server hosts the files; that exchange is governed by the hosting provider's privacy practices and is not under ForgeLM's control. We do not log this traffic ourselves.

Third-party assets

The site loads:

  • Google Fonts — the Inter and JetBrains Mono typefaces. Google may log requests as described in their privacy policy.
  • jsDelivr — Mermaid 10.9.4 is loaded on the Guide page only to render the architecture / flow diagrams. jsDelivr may log requests as described in their privacy policy.
  • Formspree — only when you submit the contact form, as described above.

Nothing else is loaded from a third party. There is no Google Analytics, no Facebook pixel, no Hotjar, no Mixpanel, no whatever-the-current-tracker is.

Children

This site is aimed at developers, ML engineers, and compliance professionals. It is not directed at children under 16.

Your rights (GDPR / KVKK)

If you believe we hold personal data about you and you want it accessed, corrected, or deleted, write to us via the contact form (or GitHub Issues) and we will respond within 30 days. Because the only personal data we hold is what you sent in the contact form, deletion is straightforward: we delete the email thread.

The toolkit itself

The ForgeLM Python package — installed via pip install forgelm — does not phone home. It does not collect telemetry, does not send usage data anywhere, and works offline (air-gap-capable). Models, datasets, and tokenizers you reference in your YAML config are downloaded only at your explicit request and only from sources you specify.

Data-subject rights you can ship in your own pipeline

If your organisation deploys ForgeLM, the toolkit gives you concrete primitives for the data-subject rights side of GDPR: forgelm reverse-pii finds the lines that contain an identifier you supply (an input to an Article 15 access response), and forgelm purge deletes rows by id from a training corpus and records the erasure in the append-only audit log (one step of an Article 17 erasure; trained weights are not changed). On top of that, ForgeLM is aligned with ISO 27001 / SOC 2 Type II controls — see the Compliance page for the full evidence map.

Changes

If we update this page, the date at the top changes. Material changes are noted in the project's CHANGELOG.md.

Questions

Open the contact form or a GitHub Discussion.

" }); Object.assign(T.tr, { @@ -4353,7 +4353,7 @@ "privacy.header.eyebrow": "Gizlilik", "privacy.header.title": "Gizlilik politikası", "privacy.header.lead": "ForgeLM, kendi altyapınızda çalıştırdığınız açık kaynaklı bir yazılımdır. Bu sayfa web sitesinin ne yaptığını açıklar — toolkit'i değil.", - "privacy.body": "

Kısa versiyon

Bu site statik HTML'dir. Analitik, izleme pixel'i, reklam takipçisi, A/B test, fingerprinting veya üçüncü-taraf telemetri çalıştırmıyoruz. Burada üç üçüncü-taraf isteği gerçekleşebilir: Google Fonts her sayfa yüklemesinde alınır (Inter ve JetBrains Mono yazıtipleri); Mermaid yalnızca Rehber sayfasında jsDelivr'dan yüklenir (diyagramları render etmek için); ve Formspree yalnızca iletişim formunu gönderdiğinizde. Üçü de aşağıda detaylıdır; başka hiçbir şey yok.

Neler topluyoruz

Normal gezinmeden

Hiçbir şey. Hiçbir cookie set edilmez. Cihazınızda yalnızca iki değer localStorage'a yazılır:

  • forgelm-lang — seçtiğiniz arayüz dili (en, tr, de, fr, es, zh).
  • forgelm-theme — seçtiğiniz renk teması (dark veya light).

İkisi de cihazınızda kalır ve hiçbir yere iletilmez. Tarayıcı depolamasını temizlemek bunları siler.

İletişim formundan

İletişim sayfasındaki formu gönderdiğinizde girdiğiniz değerler (isim, e-posta, organizasyon, konu başlığı, konu, mesaj) Formspree'ye gönderilir; Formspree de bunları proje yürütücüsüne iletir. Formspree'nin gizlilik uygulamaları kendi politikasıyla yönetilir: formspree.io/legal/privacy-policy.

E-postanız yalnızca mesajınıza cevap vermek için kullanılır. Mailing listesine eklenmez, üçüncü taraflarla paylaşılmaz, görüşme sona erdikten sonra saklanmaz.

Hosting

Site statik dosya olarak servis edilir. Tarayıcınız, dosyaları barındıran CDN / web sunucusuyla standart istek header'larını (User-Agent, Accept-Language, IP) takas eder; bu takas hosting sağlayıcısının gizlilik uygulamalarıyla yönetilir ve ForgeLM'in kontrolünde değildir. Bu trafiği biz kendimiz loglamıyoruz.

Üçüncü-taraf varlıklar

Site şunları yükler:

  • Google Fonts — Inter ve JetBrains Mono yazıtipleri. Google istekleri kendi gizlilik politikasında açıklandığı gibi loglayabilir.
  • jsDelivr — Mermaid 10.9.4 yalnızca Rehber sayfasında mimari / akış diyagramlarını render etmek için yüklenir. jsDelivr istekleri kendi gizlilik politikasında açıklandığı gibi loglayabilir.
  • Formspree — yalnızca iletişim formunu gönderdiğinizde, yukarıda açıklandığı gibi.

Üçüncü taraftan başka hiçbir şey yüklenmez. Google Analytics yok, Facebook pixel yok, Hotjar yok, Mixpanel yok, güncel-takipçi-her-neyse yok.

Çocuklar

Bu site geliştiricilere, ML mühendislerine ve compliance profesyonellerine yöneliktir. 16 yaş altındaki çocukları hedeflemez.

Haklarınız (KVKK / GDPR)

Hakkınızda kişisel veri tuttuğumuzu düşünüyor ve erişim, düzeltme veya silme istiyorsanız iletişim formundan (veya GitHub Issues üzerinden) bize yazın; 30 gün içinde döneriz. Tuttuğumuz tek kişisel veri iletişim formundan gönderdikleriniz olduğu için silme kolay: e-posta thread'ini sileriz.

Toolkit'in kendisi

pip install forgelm ile kurulan ForgeLM Python paketi eve telefon etmez. Telemetri toplamaz, kullanım verisi göndermez, offline çalışır (air-gap uyumlu). YAML konfigürasyonunuzda referans verdiğiniz modeller, veri setleri ve tokenizer'lar yalnızca açık talebinizle ve yalnızca sizin belirttiğiniz kaynaklardan indirilir.

Kendi pipeline'ınızda sevk edebileceğiniz veri sahibi hakları

Organizasyonunuz ForgeLM dağıtıyorsa, toolkit GDPR'ın veri sahibi hakları tarafına somut primitifler sunar: forgelm reverse-pii bir özne tanımlayıcısının düz-metin kalıntılarını veya tuzlanmış-digest eşleşmelerini corpus'ta tarar ve geçtiği her satırı raporlar (Madde 15 erişim) ve forgelm purge bu satırları eğitim corpus'unuzdan siler ve silmeyi append-only audit log'a damgalar (Madde 17 silme). Üstüne, ForgeLM ISO 27001 / SOC 2 Type II kontrolleriyle hizalıdır — tam kanıt haritası için Uyumluluk sayfasına bakın.

Değişiklikler

Bu sayfayı güncellersek yukarıdaki tarih değişir. Maddi değişiklikler projenin CHANGELOG.md'sine düşer.

Sorular

İletişim formunu veya bir GitHub Discussion'ı açın.

" + "privacy.body": "

Kısa versiyon

Bu site statik HTML'dir. Analitik, izleme pixel'i, reklam takipçisi, A/B test, fingerprinting veya üçüncü-taraf telemetri çalıştırmıyoruz. Burada üç üçüncü-taraf isteği gerçekleşebilir: Google Fonts her sayfa yüklemesinde alınır (Inter ve JetBrains Mono yazıtipleri); Mermaid yalnızca Rehber sayfasında jsDelivr'dan yüklenir (diyagramları render etmek için); ve Formspree yalnızca iletişim formunu gönderdiğinizde. Üçü de aşağıda detaylıdır; başka hiçbir şey yok.

Neler topluyoruz

Normal gezinmeden

Hiçbir şey. Hiçbir cookie set edilmez. Cihazınızda yalnızca iki değer localStorage'a yazılır:

  • forgelm-lang — seçtiğiniz arayüz dili (en, tr, de, fr, es, zh).
  • forgelm-theme — seçtiğiniz renk teması (dark veya light).

İkisi de cihazınızda kalır ve hiçbir yere iletilmez. Tarayıcı depolamasını temizlemek bunları siler.

İletişim formundan

İletişim sayfasındaki formu gönderdiğinizde girdiğiniz değerler (isim, e-posta, organizasyon, konu başlığı, konu, mesaj) Formspree'ye gönderilir; Formspree de bunları proje yürütücüsüne iletir. Formspree'nin gizlilik uygulamaları kendi politikasıyla yönetilir: formspree.io/legal/privacy-policy.

E-postanız yalnızca mesajınıza cevap vermek için kullanılır. Mailing listesine eklenmez, üçüncü taraflarla paylaşılmaz, görüşme sona erdikten sonra saklanmaz.

Hosting

Site statik dosya olarak servis edilir. Tarayıcınız, dosyaları barındıran CDN / web sunucusuyla standart istek header'larını (User-Agent, Accept-Language, IP) takas eder; bu takas hosting sağlayıcısının gizlilik uygulamalarıyla yönetilir ve ForgeLM'in kontrolünde değildir. Bu trafiği biz kendimiz loglamıyoruz.

Üçüncü-taraf varlıklar

Site şunları yükler:

  • Google Fonts — Inter ve JetBrains Mono yazıtipleri. Google istekleri kendi gizlilik politikasında açıklandığı gibi loglayabilir.
  • jsDelivr — Mermaid 10.9.4 yalnızca Rehber sayfasında mimari / akış diyagramlarını render etmek için yüklenir. jsDelivr istekleri kendi gizlilik politikasında açıklandığı gibi loglayabilir.
  • Formspree — yalnızca iletişim formunu gönderdiğinizde, yukarıda açıklandığı gibi.

Üçüncü taraftan başka hiçbir şey yüklenmez. Google Analytics yok, Facebook pixel yok, Hotjar yok, Mixpanel yok, güncel-takipçi-her-neyse yok.

Çocuklar

Bu site geliştiricilere, ML mühendislerine ve compliance profesyonellerine yöneliktir. 16 yaş altındaki çocukları hedeflemez.

Haklarınız (KVKK / GDPR)

Hakkınızda kişisel veri tuttuğumuzu düşünüyor ve erişim, düzeltme veya silme istiyorsanız iletişim formundan (veya GitHub Issues üzerinden) bize yazın; 30 gün içinde döneriz. Tuttuğumuz tek kişisel veri iletişim formundan gönderdikleriniz olduğu için silme kolay: e-posta thread'ini sileriz.

Toolkit'in kendisi

pip install forgelm ile kurulan ForgeLM Python paketi eve telefon etmez. Telemetri toplamaz, kullanım verisi göndermez, offline çalışır (air-gap uyumlu). YAML konfigürasyonunuzda referans verdiğiniz modeller, veri setleri ve tokenizer'lar yalnızca açık talebinizle ve yalnızca sizin belirttiğiniz kaynaklardan indirilir.

Kendi pipeline'ınızda uygulayabileceğiniz veri sahibi hakları

Organizasyonunuz ForgeLM dağıtıyorsa, toolkit GDPR'ın veri sahibi hakları tarafına somut primitifler sunar: forgelm reverse-pii verdiğiniz bir tanımlayıcıyı içeren satırları bulur (Madde 15 erişim yanıtının bir girdisi) ve forgelm purge bir eğitim corpus'undan satırları id ile siler ve silmeyi append-only audit log'a kaydeder (Madde 17 kapsamındaki silmenin bir adımı; eğitilmiş ağırlıklar değişmez). Üstüne, ForgeLM ISO 27001 / SOC 2 Type II kontrolleriyle hizalıdır — tam kanıt haritası için Uyumluluk sayfasına bakın.

Değişiklikler

Bu sayfayı güncellersek yukarıdaki tarih değişir. Maddi değişiklikler projenin CHANGELOG.md'sine düşer.

Sorular

İletişim formunu veya bir GitHub Discussion'ı açın.

" }); Object.assign(T.de, { @@ -4362,7 +4362,7 @@ "privacy.header.eyebrow": "Datenschutz", "privacy.header.title": "Datenschutzerklärung", "privacy.header.lead": "ForgeLM ist Open-Source-Software, die Sie auf Ihrer eigenen Infrastruktur betreiben. Diese Seite behandelt, was diese Website tut — nicht das Toolkit selbst.", - "privacy.body": "

Kurzversion

Diese Seite ist statisches HTML. Wir betreiben keine Analytik, keine Tracking-Pixel, keine Werbe-Tracker, keine A/B-Tests, kein Fingerprinting und keine Drittanbieter-Telemetrie. Hier können drei Drittanbieter-Anfragen erfolgen: Google Fonts wird bei jedem Seitenaufruf abgerufen (die Schriften Inter und JetBrains Mono); Mermaid wird ausschließlich auf der Guide-Seite von jsDelivr geladen (zum Rendern der Diagramme); und Formspree nur, wenn Sie das Kontaktformular absenden. Alle drei werden unten detailliert beschrieben; sonst nichts.

Was wir erfassen

Beim normalen Surfen

Nichts. Es werden keine Cookies gesetzt. Auf Ihrem Gerät werden zwei Werte in localStorage gespeichert:

  • forgelm-lang — Ihre gewählte Oberflächensprache (en, tr, de, fr, es, zh).
  • forgelm-theme — Ihr gewähltes Farbschema (dark oder light).

Beide bleiben auf Ihrem Gerät und werden nie übertragen. Das Leeren Ihres Browser-Speichers entfernt sie.

Vom Kontaktformular

Wenn Sie das Formular auf der Kontakt-Seite absenden, werden die eingegebenen Werte (Name, E-Mail, Organisation, Thema, Betreff, Nachricht) an Formspree gesendet, das sie an den Projekt-Maintainer weiterleitet. Die Datenschutzpraktiken von Formspree unterliegen ihrer eigenen Richtlinie unter formspree.io/legal/privacy-policy.

Ihre E-Mail wird ausschließlich zur Beantwortung Ihrer Nachricht verwendet. Wir nehmen sie nicht in eine Mailingliste auf, geben sie nicht an Dritte weiter und behalten sie nach Abschluss der Konversation nicht zurück.

Hosting

Die Seite wird als statische Dateien ausgeliefert. Ihr Browser tauscht Standard-Request-Header (User-Agent, Accept-Language, IP-Adresse) mit dem CDN / Webserver aus, der die Dateien hostet; dieser Austausch unterliegt den Datenschutzpraktiken des Hosting-Anbieters und liegt nicht in der Kontrolle von ForgeLM. Wir loggen diesen Verkehr selbst nicht.

Drittanbieter-Assets

Die Seite lädt:

  • Google Fonts — die Schriften Inter und JetBrains Mono. Google kann Anfragen wie in seiner Datenschutzrichtlinie beschrieben loggen.
  • jsDelivr — Mermaid 10.9.4 wird ausschließlich auf der Guide-Seite geladen, um die Architektur- / Flussdiagramme zu rendern. jsDelivr kann Anfragen wie in seiner Datenschutzrichtlinie beschrieben loggen.
  • Formspree — nur wenn Sie das Kontaktformular absenden, wie oben beschrieben.

Sonst wird nichts von Drittanbietern geladen. Kein Google Analytics, kein Facebook-Pixel, kein Hotjar, kein Mixpanel, kein wie-auch-immer-aktueller-Tracker.

Kinder

Diese Seite richtet sich an Entwickler, ML-Ingenieure und Compliance-Fachleute. Sie richtet sich nicht an Kinder unter 16.

Ihre Rechte (DSGVO / KVKK)

Wenn Sie der Meinung sind, dass wir personenbezogene Daten über Sie speichern, und Sie Auskunft, Berichtigung oder Löschung wünschen, schreiben Sie uns über das Kontaktformular (oder GitHub Issues); wir antworten innerhalb von 30 Tagen. Da die einzigen personenbezogenen Daten, die wir halten, das sind, was Sie im Kontaktformular gesendet haben, ist die Löschung einfach: Wir löschen den E-Mail-Thread.

Das Toolkit selbst

Das ForgeLM-Python-Paket — installiert über pip install forgelm — telefoniert nicht nach Hause. Es sammelt keine Telemetrie, sendet keine Nutzungsdaten irgendwohin und arbeitet offline (Air-Gap-fähig). Modelle, Datensätze und Tokenizer, die Sie in Ihrer YAML-Konfiguration referenzieren, werden nur auf Ihre ausdrückliche Anforderung und nur aus von Ihnen angegebenen Quellen heruntergeladen.

Datensubjekt-Rechte, die Sie in Ihrer eigenen Pipeline ausliefern können

Wenn Ihre Organisation ForgeLM einsetzt, liefert das Toolkit konkrete Primitive für die Datensubjekt-Rechte-Seite der DSGVO: forgelm reverse-pii durchsucht den Korpus nach Klartextresten oder gesalzenen Digest-Treffern einer Subjekt-Kennung und meldet jede Zeile, in der diese Person erscheint (Artikel 15 Auskunft), und forgelm purge löscht diese Zeilen aus Ihren Trainingskorpora und stempelt die Löschung in das Append-only-Audit-Log (Artikel 17 Löschung). Darüber hinaus ist ForgeLM mit ISO 27001 / SOC 2 Type II Kontrollen aligned — siehe die Compliance-Seite für die vollständige Nachweis-Karte.

Änderungen

Wenn wir diese Seite aktualisieren, ändert sich das Datum oben. Wesentliche Änderungen werden im CHANGELOG.md des Projekts vermerkt.

Fragen

Öffnen Sie das Kontaktformular oder eine GitHub Discussion.

" + "privacy.body": "

Kurzversion

Diese Seite ist statisches HTML. Wir betreiben keine Analytik, keine Tracking-Pixel, keine Werbe-Tracker, keine A/B-Tests, kein Fingerprinting und keine Drittanbieter-Telemetrie. Hier können drei Drittanbieter-Anfragen erfolgen: Google Fonts wird bei jedem Seitenaufruf abgerufen (die Schriften Inter und JetBrains Mono); Mermaid wird ausschließlich auf der Guide-Seite von jsDelivr geladen (zum Rendern der Diagramme); und Formspree nur, wenn Sie das Kontaktformular absenden. Alle drei werden unten detailliert beschrieben; sonst nichts.

Was wir erfassen

Beim normalen Surfen

Nichts. Es werden keine Cookies gesetzt. Auf Ihrem Gerät werden zwei Werte in localStorage gespeichert:

  • forgelm-lang — Ihre gewählte Oberflächensprache (en, tr, de, fr, es, zh).
  • forgelm-theme — Ihr gewähltes Farbschema (dark oder light).

Beide bleiben auf Ihrem Gerät und werden nie übertragen. Das Leeren Ihres Browser-Speichers entfernt sie.

Vom Kontaktformular

Wenn Sie das Formular auf der Kontakt-Seite absenden, werden die eingegebenen Werte (Name, E-Mail, Organisation, Thema, Betreff, Nachricht) an Formspree gesendet, das sie an den Projekt-Maintainer weiterleitet. Die Datenschutzpraktiken von Formspree unterliegen ihrer eigenen Richtlinie unter formspree.io/legal/privacy-policy.

Ihre E-Mail wird ausschließlich zur Beantwortung Ihrer Nachricht verwendet. Wir nehmen sie nicht in eine Mailingliste auf, geben sie nicht an Dritte weiter und behalten sie nach Abschluss der Konversation nicht zurück.

Hosting

Die Seite wird als statische Dateien ausgeliefert. Ihr Browser tauscht Standard-Request-Header (User-Agent, Accept-Language, IP-Adresse) mit dem CDN / Webserver aus, der die Dateien hostet; dieser Austausch unterliegt den Datenschutzpraktiken des Hosting-Anbieters und liegt nicht in der Kontrolle von ForgeLM. Wir loggen diesen Verkehr selbst nicht.

Drittanbieter-Assets

Die Seite lädt:

  • Google Fonts — die Schriften Inter und JetBrains Mono. Google kann Anfragen wie in seiner Datenschutzrichtlinie beschrieben loggen.
  • jsDelivr — Mermaid 10.9.4 wird ausschließlich auf der Guide-Seite geladen, um die Architektur- / Flussdiagramme zu rendern. jsDelivr kann Anfragen wie in seiner Datenschutzrichtlinie beschrieben loggen.
  • Formspree — nur wenn Sie das Kontaktformular absenden, wie oben beschrieben.

Sonst wird nichts von Drittanbietern geladen. Kein Google Analytics, kein Facebook-Pixel, kein Hotjar, kein Mixpanel, kein wie-auch-immer-aktueller-Tracker.

Kinder

Diese Seite richtet sich an Entwickler, ML-Ingenieure und Compliance-Fachleute. Sie richtet sich nicht an Kinder unter 16.

Ihre Rechte (DSGVO / KVKK)

Wenn Sie der Meinung sind, dass wir personenbezogene Daten über Sie speichern, und Sie Auskunft, Berichtigung oder Löschung wünschen, schreiben Sie uns über das Kontaktformular (oder GitHub Issues); wir antworten innerhalb von 30 Tagen. Da die einzigen personenbezogenen Daten, die wir halten, das sind, was Sie im Kontaktformular gesendet haben, ist die Löschung einfach: Wir löschen den E-Mail-Thread.

Das Toolkit selbst

Das ForgeLM-Python-Paket — installiert über pip install forgelm — telefoniert nicht nach Hause. Es sammelt keine Telemetrie, sendet keine Nutzungsdaten irgendwohin und arbeitet offline (Air-Gap-fähig). Modelle, Datensätze und Tokenizer, die Sie in Ihrer YAML-Konfiguration referenzieren, werden nur auf Ihre ausdrückliche Anforderung und nur aus von Ihnen angegebenen Quellen heruntergeladen.

Betroffenenrechte, die Sie in Ihrer eigenen Pipeline umsetzen können

Wenn Ihre Organisation ForgeLM einsetzt, liefert das Toolkit Bausteine für die Betroffenenrechte der DSGVO: forgelm reverse-pii findet die Zeilen, die eine von Ihnen angegebene Kennung enthalten (ein Baustein der Auskunft nach Art. 15 DSGVO), und forgelm purge löscht Zeilen anhand ihrer ID aus einem Trainingskorpus und protokolliert die Löschung im Append-only-Audit-Log (ein Schritt der Löschung nach Art. 17 DSGVO; trainierte Gewichte bleiben unverändert). Darüber hinaus ist ForgeLM mit ISO 27001 / SOC 2 Type II Kontrollen aligned — siehe die Compliance-Seite für die vollständige Nachweis-Karte.

Änderungen

Wenn wir diese Seite aktualisieren, ändert sich das Datum oben. Wesentliche Änderungen werden im CHANGELOG.md des Projekts vermerkt.

Fragen

Öffnen Sie das Kontaktformular oder eine GitHub Discussion.

" }); Object.assign(T.fr, { @@ -4371,7 +4371,7 @@ "privacy.header.eyebrow": "Confidentialité", "privacy.header.title": "Politique de confidentialité", "privacy.header.lead": "ForgeLM est un logiciel open source que vous exécutez sur votre propre infrastructure. Cette page couvre ce que ce site web fait — pas la boîte à outils elle-même.", - "privacy.body": "

Version courte

Ce site est du HTML statique. Nous n'exécutons pas d'analytics, de pixels de tracking, de trackers publicitaires, d'A/B tests, de fingerprinting ou de télémétrie tierce. Trois requêtes tierces peuvent se produire ici : Google Fonts est récupéré à chaque chargement de page (les polices Inter et JetBrains Mono) ; Mermaid est chargé depuis jsDelivr uniquement sur la page Guide (pour rendre les diagrammes) ; et Formspree uniquement lorsque vous soumettez le formulaire de contact. Les trois sont détaillés ci-dessous ; rien d'autre.

Ce que nous collectons

De la navigation normale

Rien. Aucun cookie n'est défini. Deux valeurs sont stockées dans localStorage sur votre appareil :

  • forgelm-lang — la langue d'interface choisie (en, tr, de, fr, es, zh).
  • forgelm-theme — votre thème de couleur choisi (dark ou light).

Les deux restent sur votre appareil et ne sont jamais transmises. Vider le stockage du navigateur les supprime.

Du formulaire de contact

Lorsque vous soumettez le formulaire sur la page Contact, les valeurs saisies (nom, e-mail, organisation, sujet, objet, message) sont envoyées à Formspree, qui les transmet au mainteneur du projet. Les pratiques de confidentialité de Formspree sont régies par leur propre politique sur formspree.io/legal/privacy-policy.

Votre e-mail est utilisé uniquement pour répondre à votre message. Nous ne l'ajoutons pas à une liste de diffusion, ne le partageons pas avec des tiers et ne le conservons pas après la résolution de la conversation.

Hébergement

Ce site est servi sous forme de fichiers statiques. Votre navigateur échangera des en-têtes de requête standard (User-Agent, Accept-Language, adresse IP) avec le CDN / serveur web qui héberge les fichiers ; cet échange est régi par les pratiques de confidentialité du fournisseur d'hébergement et n'est pas sous le contrôle de ForgeLM. Nous ne loggons pas ce trafic nous-mêmes.

Ressources tierces

Le site charge :

  • Google Fonts — les polices Inter et JetBrains Mono. Google peut logger les requêtes comme décrit dans sa politique de confidentialité.
  • jsDelivr — Mermaid 10.9.4 est chargé uniquement sur la page Guide pour rendre les diagrammes d'architecture / flux. jsDelivr peut logger les requêtes comme décrit dans sa politique de confidentialité.
  • Formspree — uniquement lorsque vous soumettez le formulaire de contact, comme décrit ci-dessus.

Rien d'autre n'est chargé depuis un tiers. Pas de Google Analytics, pas de pixel Facebook, pas de Hotjar, pas de Mixpanel, pas de tracker-du-moment-quel-qu'il-soit.

Enfants

Ce site s'adresse aux développeurs, ingénieurs ML et professionnels de la conformité. Il n'est pas destiné aux enfants de moins de 16 ans.

Vos droits (RGPD / KVKK)

Si vous pensez que nous détenons des données personnelles vous concernant et que vous voulez y accéder, les corriger ou les supprimer, écrivez-nous via le formulaire de contact (ou GitHub Issues) et nous répondrons dans les 30 jours. Comme les seules données personnelles que nous détenons sont celles que vous avez envoyées dans le formulaire de contact, la suppression est simple : nous supprimons le fil d'e-mail.

La boîte à outils elle-même

Le paquet Python ForgeLM — installé via pip install forgelm — ne « téléphone pas à la maison ». Il ne collecte pas de télémétrie, n'envoie pas de données d'usage où que ce soit et fonctionne hors-ligne (compatible air-gap). Les modèles, datasets et tokenizers que vous référencez dans votre config YAML ne sont téléchargés qu'à votre demande explicite et uniquement depuis les sources que vous spécifiez.

Droits des personnes concernées que vous pouvez livrer dans votre propre pipeline

Si votre organisation déploie ForgeLM, le toolkit fournit des primitives concrètes pour le volet droits des personnes concernées du RGPD : forgelm reverse-pii scanne le corpus à la recherche de résidus en clair ou de digests salés d'un identifiant de personne et signale chaque ligne où elle apparaît (Article 15 accès), et forgelm purge supprime ces lignes de vos corpus d'entraînement et estampille la suppression dans le log d'audit append-only (Article 17 effacement). De plus, ForgeLM est aligné avec les contrôles ISO 27001 / SOC 2 Type II — voir la page Compliance pour la cartographie complète des preuves.

Modifications

Si nous mettons cette page à jour, la date en haut change. Les changements importants sont notés dans le CHANGELOG.md du projet.

Questions

Ouvrez le formulaire de contact ou une GitHub Discussion.

" + "privacy.body": "

Version courte

Ce site est du HTML statique. Nous n'exécutons pas d'analytics, de pixels de tracking, de trackers publicitaires, d'A/B tests, de fingerprinting ou de télémétrie tierce. Trois requêtes tierces peuvent se produire ici : Google Fonts est récupéré à chaque chargement de page (les polices Inter et JetBrains Mono) ; Mermaid est chargé depuis jsDelivr uniquement sur la page Guide (pour rendre les diagrammes) ; et Formspree uniquement lorsque vous soumettez le formulaire de contact. Les trois sont détaillés ci-dessous ; rien d'autre.

Ce que nous collectons

De la navigation normale

Rien. Aucun cookie n'est défini. Deux valeurs sont stockées dans localStorage sur votre appareil :

  • forgelm-lang — la langue d'interface choisie (en, tr, de, fr, es, zh).
  • forgelm-theme — votre thème de couleur choisi (dark ou light).

Les deux restent sur votre appareil et ne sont jamais transmises. Vider le stockage du navigateur les supprime.

Du formulaire de contact

Lorsque vous soumettez le formulaire sur la page Contact, les valeurs saisies (nom, e-mail, organisation, sujet, objet, message) sont envoyées à Formspree, qui les transmet au mainteneur du projet. Les pratiques de confidentialité de Formspree sont régies par leur propre politique sur formspree.io/legal/privacy-policy.

Votre e-mail est utilisé uniquement pour répondre à votre message. Nous ne l'ajoutons pas à une liste de diffusion, ne le partageons pas avec des tiers et ne le conservons pas après la résolution de la conversation.

Hébergement

Ce site est servi sous forme de fichiers statiques. Votre navigateur échangera des en-têtes de requête standard (User-Agent, Accept-Language, adresse IP) avec le CDN / serveur web qui héberge les fichiers ; cet échange est régi par les pratiques de confidentialité du fournisseur d'hébergement et n'est pas sous le contrôle de ForgeLM. Nous ne loggons pas ce trafic nous-mêmes.

Ressources tierces

Le site charge :

  • Google Fonts — les polices Inter et JetBrains Mono. Google peut logger les requêtes comme décrit dans sa politique de confidentialité.
  • jsDelivr — Mermaid 10.9.4 est chargé uniquement sur la page Guide pour rendre les diagrammes d'architecture / flux. jsDelivr peut logger les requêtes comme décrit dans sa politique de confidentialité.
  • Formspree — uniquement lorsque vous soumettez le formulaire de contact, comme décrit ci-dessus.

Rien d'autre n'est chargé depuis un tiers. Pas de Google Analytics, pas de pixel Facebook, pas de Hotjar, pas de Mixpanel, pas de tracker-du-moment-quel-qu'il-soit.

Enfants

Ce site s'adresse aux développeurs, ingénieurs ML et professionnels de la conformité. Il n'est pas destiné aux enfants de moins de 16 ans.

Vos droits (RGPD / KVKK)

Si vous pensez que nous détenons des données personnelles vous concernant et que vous voulez y accéder, les corriger ou les supprimer, écrivez-nous via le formulaire de contact (ou GitHub Issues) et nous répondrons dans les 30 jours. Comme les seules données personnelles que nous détenons sont celles que vous avez envoyées dans le formulaire de contact, la suppression est simple : nous supprimons le fil d'e-mail.

La boîte à outils elle-même

Le paquet Python ForgeLM — installé via pip install forgelm — ne « téléphone pas à la maison ». Il ne collecte pas de télémétrie, n'envoie pas de données d'usage où que ce soit et fonctionne hors-ligne (compatible air-gap). Les modèles, datasets et tokenizers que vous référencez dans votre config YAML ne sont téléchargés qu'à votre demande explicite et uniquement depuis les sources que vous spécifiez.

Droits des personnes concernées que vous pouvez mettre en œuvre dans votre propre pipeline

Si votre organisation déploie ForgeLM, le toolkit fournit des primitives concrètes pour le volet droits des personnes concernées du RGPD : forgelm reverse-pii trouve les lignes qui contiennent un identifiant que vous fournissez (un élément de la réponse à une demande d'accès, article 15), et forgelm purge supprime, par identifiant (id), des lignes d'un corpus d'entraînement et consigne l'effacement dans le log d'audit append-only (une étape de l'effacement, article 17 ; les poids entraînés ne changent pas). De plus, ForgeLM est aligné avec les contrôles ISO 27001 / SOC 2 Type II — voir la page Compliance pour la cartographie complète des preuves.

Modifications

Si nous mettons cette page à jour, la date en haut change. Les changements importants sont notés dans le CHANGELOG.md du projet.

Questions

Ouvrez le formulaire de contact ou une GitHub Discussion.

" }); Object.assign(T.es, { @@ -4380,7 +4380,7 @@ "privacy.header.eyebrow": "Privacidad", "privacy.header.title": "Política de privacidad", "privacy.header.lead": "ForgeLM es software de código abierto que ejecuta en su propia infraestructura. Esta página cubre lo que hace este sitio web — no el toolkit en sí.", - "privacy.body": "

Versión corta

Este sitio es HTML estático. No ejecutamos analytics, píxeles de tracking, trackers publicitarios, A/B tests, fingerprinting o telemetría de terceros. Aquí pueden ocurrir tres solicitudes a terceros: Google Fonts se obtiene en cada carga de página (las tipografías Inter y JetBrains Mono); Mermaid se carga desde jsDelivr solo en la página Guía (para renderizar los diagramas); y Formspree solo cuando envía el formulario de contacto. Las tres están detalladas abajo; nada más.

Qué recopilamos

De la navegación normal

Nada. No se establecen cookies. Se almacenan dos valores en localStorage en su dispositivo:

  • forgelm-lang — su idioma de interfaz elegido (en, tr, de, fr, es, zh).
  • forgelm-theme — su tema de color elegido (dark o light).

Ambos quedan en su dispositivo y nunca se transmiten. Limpiar el almacenamiento del navegador los elimina.

Del formulario de contacto

Cuando envía el formulario en la página de Contacto, los valores que escribió (nombre, email, organización, tema, asunto, mensaje) se envían a Formspree, que los reenvía al mantenedor del proyecto. Las prácticas de privacidad de Formspree se rigen por su propia política en formspree.io/legal/privacy-policy.

Su email se usa solo para responder a su mensaje. No lo agregamos a una lista de correo, no lo compartimos con terceros y no lo retenemos después de que la conversación se resuelve.

Hosting

Este sitio se sirve como archivos estáticos. Su navegador intercambiará cabeceras de solicitud estándar (User-Agent, Accept-Language, dirección IP) con el CDN / servidor web que aloja los archivos; ese intercambio se rige por las prácticas de privacidad del proveedor de hosting y no está bajo el control de ForgeLM. No registramos este tráfico nosotros mismos.

Recursos de terceros

El sitio carga:

  • Google Fonts — las tipografías Inter y JetBrains Mono. Google puede registrar las solicitudes como se describe en su política de privacidad.
  • jsDelivr — Mermaid 10.9.4 se carga solo en la página Guía para renderizar los diagramas de arquitectura / flujo. jsDelivr puede registrar las solicitudes como se describe en su política de privacidad.
  • Formspree — solo cuando envía el formulario de contacto, como se describe arriba.

Nada más se carga desde un tercero. No hay Google Analytics, no hay píxel de Facebook, no hay Hotjar, no hay Mixpanel, no hay tracker-del-momento-sea-cual-sea.

Niños

Este sitio está dirigido a desarrolladores, ingenieros de ML y profesionales del cumplimiento. No está dirigido a niños menores de 16 años.

Sus derechos (RGPD / KVKK)

Si cree que tenemos datos personales sobre usted y desea acceder, corregir o eliminarlos, escríbanos vía el formulario de contacto (o GitHub Issues) y responderemos en 30 días. Dado que los únicos datos personales que tenemos son lo que envió en el formulario de contacto, la eliminación es directa: borramos el hilo de email.

El toolkit en sí

El paquete Python ForgeLM — instalado vía pip install forgelm — no telefonea a casa. No recopila telemetría, no envía datos de uso a ninguna parte y funciona offline (compatible con air-gap). Los modelos, datasets y tokenizers que referencia en su config YAML se descargan solo a petición explícita suya y solo desde fuentes que usted especifica.

Derechos del interesado que puede enviar en su propio pipeline

Si su organización despliega ForgeLM, el toolkit proporciona primitivas concretas para el lado de los derechos del interesado del RGPD: forgelm reverse-pii escanea el corpus en busca de residuos en texto claro o digests salados de un identificador de sujeto y reporta cada fila en la que aparece (Artículo 15 acceso), y forgelm purge elimina esas filas de sus corpus de entrenamiento y estampa la supresión en el log de auditoría append-only (Artículo 17 supresión). Además, ForgeLM está alineado con los controles ISO 27001 / SOC 2 Type II — vea la página de Compliance para el mapa completo de pruebas.

Cambios

Si actualizamos esta página, la fecha en la parte superior cambia. Los cambios materiales se anotan en el CHANGELOG.md del proyecto.

Preguntas

Abra el formulario de contacto o una GitHub Discussion.

" + "privacy.body": "

Versión corta

Este sitio es HTML estático. No ejecutamos analytics, píxeles de tracking, trackers publicitarios, A/B tests, fingerprinting o telemetría de terceros. Aquí pueden ocurrir tres solicitudes a terceros: Google Fonts se obtiene en cada carga de página (las tipografías Inter y JetBrains Mono); Mermaid se carga desde jsDelivr solo en la página Guía (para renderizar los diagramas); y Formspree solo cuando envía el formulario de contacto. Las tres están detalladas abajo; nada más.

Qué recopilamos

De la navegación normal

Nada. No se establecen cookies. Se almacenan dos valores en localStorage en su dispositivo:

  • forgelm-lang — su idioma de interfaz elegido (en, tr, de, fr, es, zh).
  • forgelm-theme — su tema de color elegido (dark o light).

Ambos quedan en su dispositivo y nunca se transmiten. Limpiar el almacenamiento del navegador los elimina.

Del formulario de contacto

Cuando envía el formulario en la página de Contacto, los valores que escribió (nombre, email, organización, tema, asunto, mensaje) se envían a Formspree, que los reenvía al mantenedor del proyecto. Las prácticas de privacidad de Formspree se rigen por su propia política en formspree.io/legal/privacy-policy.

Su email se usa solo para responder a su mensaje. No lo agregamos a una lista de correo, no lo compartimos con terceros y no lo retenemos después de que la conversación se resuelve.

Hosting

Este sitio se sirve como archivos estáticos. Su navegador intercambiará cabeceras de solicitud estándar (User-Agent, Accept-Language, dirección IP) con el CDN / servidor web que aloja los archivos; ese intercambio se rige por las prácticas de privacidad del proveedor de hosting y no está bajo el control de ForgeLM. No registramos este tráfico nosotros mismos.

Recursos de terceros

El sitio carga:

  • Google Fonts — las tipografías Inter y JetBrains Mono. Google puede registrar las solicitudes como se describe en su política de privacidad.
  • jsDelivr — Mermaid 10.9.4 se carga solo en la página Guía para renderizar los diagramas de arquitectura / flujo. jsDelivr puede registrar las solicitudes como se describe en su política de privacidad.
  • Formspree — solo cuando envía el formulario de contacto, como se describe arriba.

Nada más se carga desde un tercero. No hay Google Analytics, no hay píxel de Facebook, no hay Hotjar, no hay Mixpanel, no hay tracker-del-momento-sea-cual-sea.

Niños

Este sitio está dirigido a desarrolladores, ingenieros de ML y profesionales del cumplimiento. No está dirigido a niños menores de 16 años.

Sus derechos (RGPD / KVKK)

Si cree que tenemos datos personales sobre usted y desea acceder, corregir o eliminarlos, escríbanos vía el formulario de contacto (o GitHub Issues) y responderemos en 30 días. Dado que los únicos datos personales que tenemos son lo que envió en el formulario de contacto, la eliminación es directa: borramos el hilo de email.

El toolkit en sí

El paquete Python ForgeLM — instalado vía pip install forgelm — no telefonea a casa. No recopila telemetría, no envía datos de uso a ninguna parte y funciona offline (compatible con air-gap). Los modelos, datasets y tokenizers que referencia en su config YAML se descargan solo a petición explícita suya y solo desde fuentes que usted especifica.

Derechos del interesado que puede implementar en su propio pipeline

Si su organización despliega ForgeLM, el toolkit proporciona primitivas concretas para el lado de los derechos del interesado del RGPD: forgelm reverse-pii encuentra las filas que contienen un identificador que usted proporciona (un elemento de la respuesta a la solicitud de acceso del Artículo 15), y forgelm purge elimina filas por id de un corpus de entrenamiento y registra la supresión en el log de auditoría append-only (un paso de la supresión conforme al Artículo 17; los pesos entrenados no cambian). Además, ForgeLM está alineado con los controles ISO 27001 / SOC 2 Type II — vea la página de Compliance para el mapa completo de pruebas.

Cambios

Si actualizamos esta página, la fecha en la parte superior cambia. Los cambios materiales se anotan en el CHANGELOG.md del proyecto.

Preguntas

Abra el formulario de contacto o una GitHub Discussion.

" }); Object.assign(T.zh, { @@ -4389,7 +4389,7 @@ "privacy.header.eyebrow": "隐私", "privacy.header.title": "隐私政策", "privacy.header.lead": "ForgeLM 是你在自己基础设施上运行的开源软件。本页讲的是这个网站做了什么 — 不是 toolkit 本身。", - "privacy.body": "

简短版

本站是静态 HTML。我们不运行分析、跟踪像素、广告跟踪器、A/B 测试、指纹识别或任何第三方遥测。本站可能发生三种第三方请求:Google Fonts 在每次页面加载时获取 (Inter 和 JetBrains Mono 字体);Mermaid 仅在指南页面从 jsDelivr 加载 (用于渲染图表);Formspree 仅在你提交联系表单时。三者均在下方详述;此外没有别的。

我们收集什么

正常浏览时

什么也没有。不设置 cookie。在你设备的 localStorage 中存储两个值:

  • forgelm-lang — 你选择的界面语言 (en、tr、de、fr、es、zh)。
  • forgelm-theme — 你选择的色彩主题 (dark 或 light)。

两者都留在你设备上,从不发送出去。清除浏览器存储就能移除它们。

从联系表单

当你在联系页提交表单时,你填写的值 (姓名、邮箱、组织、主题、标题、消息) 会发送到 Formspree,Formspree 再转发给项目维护者。Formspree 的隐私实践由其自身政策管理:formspree.io/legal/privacy-policy。

你的邮箱仅用于回复你的消息。不会加入任何邮件列表,不会与第三方共享,在对话结束之后也不保留。

托管

本站以静态文件的方式提供。你的浏览器会与托管文件的 CDN / Web 服务器交换标准请求头 (User-Agent、Accept-Language、IP 地址);该交换由托管提供商的隐私实践管理,不在 ForgeLM 的控制之下。我们自己不记录这部分流量。

第三方资源

本站加载:

  • Google Fonts — Inter 和 JetBrains Mono 字体。Google 可能按照其隐私政策记录请求。
  • jsDelivr — Mermaid 10.9.4 仅在指南页面加载,用于渲染架构 / 流程图。jsDelivr 可能按照其隐私政策记录请求。
  • Formspree — 仅在你提交联系表单时,如上所述。

除此之外不从任何第三方加载。没有 Google Analytics、没有 Facebook 像素、没有 Hotjar、没有 Mixpanel、没有那个时髦的随便什么跟踪器。

儿童

本站面向开发者、ML 工程师和合规专业人员。不面向 16 岁以下儿童。

你的权利 (GDPR / KVKK)

如果你认为我们持有关于你的个人数据,并希望访问、更正或删除它,通过联系表单 (或 GitHub Issues) 写信给我们;我们会在 30 天内回复。由于我们持有的唯一个人数据就是你在联系表单中发送的内容,删除很简单:我们删除该邮件 thread。

Toolkit 本身

通过 pip install forgelm 安装的 ForgeLM Python 包不会回拨。不收集遥测,不向任何地方发送使用数据,可离线工作 (支持气隙)。你在 YAML 配置中引用的模型、数据集和 tokenizer 仅在你的明确请求下且仅从你指定的来源下载。

你可以在自己的管道中交付的数据主体权利

如果你的组织部署 ForgeLM,这套工具为 GDPR 数据主体权利那一面提供了具体原语:forgelm reverse-pii 扫描语料库,查找主体标识的明文残留或加盐 digest 匹配,并报告该主体出现的每一行 (第 15 条访问);forgelm purge 从你的训练语料中删除这些行,并将删除盖戳到 append-only 审计日志中 (第 17 条删除)。此外,ForgeLM 与 ISO 27001 / SOC 2 Type II 控制项保持对齐 — 见合规页面查看完整证据图。

变更

如果我们更新本页,顶部日期会变化。重要变更会记入项目的 CHANGELOG.md。

问题

打开联系表单或一个 GitHub Discussion。

" + "privacy.body": "

简短版

本站是静态 HTML。我们不运行分析、跟踪像素、广告跟踪器、A/B 测试、指纹识别或任何第三方遥测。本站可能发生三种第三方请求:Google Fonts 在每次页面加载时获取 (Inter 和 JetBrains Mono 字体);Mermaid 仅在指南页面从 jsDelivr 加载 (用于渲染图表);Formspree 仅在你提交联系表单时。三者均在下方详述;此外没有别的。

我们收集什么

正常浏览时

什么也没有。不设置 cookie。在你设备的 localStorage 中存储两个值:

  • forgelm-lang — 你选择的界面语言 (en、tr、de、fr、es、zh)。
  • forgelm-theme — 你选择的色彩主题 (dark 或 light)。

两者都留在你设备上,从不发送出去。清除浏览器存储就能移除它们。

从联系表单

当你在联系页提交表单时,你填写的值 (姓名、邮箱、组织、主题、标题、消息) 会发送到 Formspree,Formspree 再转发给项目维护者。Formspree 的隐私实践由其自身政策管理:formspree.io/legal/privacy-policy。

你的邮箱仅用于回复你的消息。不会加入任何邮件列表,不会与第三方共享,在对话结束之后也不保留。

托管

本站以静态文件的方式提供。你的浏览器会与托管文件的 CDN / Web 服务器交换标准请求头 (User-Agent、Accept-Language、IP 地址);该交换由托管提供商的隐私实践管理,不在 ForgeLM 的控制之下。我们自己不记录这部分流量。

第三方资源

本站加载:

  • Google Fonts — Inter 和 JetBrains Mono 字体。Google 可能按照其隐私政策记录请求。
  • jsDelivr — Mermaid 10.9.4 仅在指南页面加载,用于渲染架构 / 流程图。jsDelivr 可能按照其隐私政策记录请求。
  • Formspree — 仅在你提交联系表单时,如上所述。

除此之外不从任何第三方加载。没有 Google Analytics、没有 Facebook 像素、没有 Hotjar、没有 Mixpanel、没有那个时髦的随便什么跟踪器。

儿童

本站面向开发者、ML 工程师和合规专业人员。不面向 16 岁以下儿童。

你的权利 (GDPR / KVKK)

如果你认为我们持有关于你的个人数据,并希望访问、更正或删除它,通过联系表单 (或 GitHub Issues) 写信给我们;我们会在 30 天内回复。由于我们持有的唯一个人数据就是你在联系表单中发送的内容,删除很简单:我们删除该邮件 thread。

Toolkit 本身

通过 pip install forgelm 安装的 ForgeLM Python 包不会回拨。不收集遥测,不向任何地方发送使用数据,可离线工作 (支持气隙)。你在 YAML 配置中引用的模型、数据集和 tokenizer 仅在你的明确请求下且仅从你指定的来源下载。

你可以在自己的管道中落实的数据主体权利

如果你的组织部署 ForgeLM,这套工具为 GDPR 数据主体权利那一面提供了具体原语:forgelm reverse-pii 查找包含你所提供标识符的行 (对第 15 条访问请求答复的输入之一);forgelm purge 按 id 从训练语料中删除行,并将该删除记录到 append-only 审计日志中 (第 17 条所涉删除的其中一步,已训练的权重不会改变)。此外,ForgeLM 与 ISO 27001 / SOC 2 Type II 控制项保持对齐 — 见合规页面查看完整证据图。

变更

如果我们更新本页,顶部日期会变化。重要变更会记入项目的 CHANGELOG.md。

问题

打开联系表单或一个 GitHub Discussion。

" }); /* ── Terms page (terms.html) ─────────────────────────────── */ @@ -4452,9 +4452,9 @@ /* ── Phase 22 / pass-4: backfill missing compliance + features keys (was H6 i18n gap) ── */ Object.assign(T.de, { "compliance.gdpr15.title": "Auskunftsrecht", - "compliance.gdpr15.body": "forgelm reverse-pii durchsucht maskierte JSONL-Korpora nach Klartextresten oder gesalzenen SHA-256-Digests einer Betroffenenkennung und meldet jede Zeile jedes Splits, in der die betroffene Person erscheint. Es findet nur, was wörtlich in den gescannten Dateien steht: Rohquellen und das trainierte Modell werden nicht durchsucht, und die Bearbeitung eines Auskunftsersuchens bleibt Ihr Prozess.", + "compliance.gdpr15.body": "forgelm reverse-pii durchsucht JSONL-Korpora nach der Kennung einer betroffenen Person – im Klartext oder in Form eines gesalzenen SHA-256-Digests, den eine externe Maskierungs-Pipeline geschrieben hat – und meldet jede Zeile der durchsuchten Dateien, die sie enthält. Standardmäßig ist die Suche wortgetreu und unterscheidet Groß- und Kleinschreibung (--type custom nimmt einen regulären Ausdruck). Das ist ein Baustein Ihrer Auskunft nach Art. 15 DSGVO, die außerdem eine Kopie der Daten und die Informationen nach Art. 15 Abs. 1 Buchst. a bis h DSGVO enthalten muss; Rohquellen und das trainierte Modell werden nicht durchsucht.", "compliance.gdpr17.title": "Recht auf Löschung", - "compliance.gdpr17.body": "forgelm purge löscht die Zeilen einer betroffenen Person aus Trainingskorpora und stempelt data.erasure_requested + data.erasure_completed-Ereignisse in das Append-only-Audit-Log, sodass Läufe nach der Löschung forensisch mit dem Löschereignis verknüpft sind.", + "compliance.gdpr17.body": "forgelm purge löscht Zeilen anhand ihrer ID aus einem Trainingskorpus und protokolliert die Ereignisse data.erasure_requested + data.erasure_completed im Append-only-Audit-Log. Das ist ein Schritt einer Löschung nach Art. 17 DSGVO: Trainierte Gewichte, Quelldokumente und Kopien an anderen Orten bleiben unverändert.", "compliance.iso.title": "Aligned, nicht zertifiziert", "compliance.iso.eyebrow": "ISO 27001 · SOC 2 Type II", "compliance.iso.lead": "Software kann nicht ISO 27001 oder SOC 2 zertifiziert werden — nur Organisationen. ForgeLM ist aligned mit den Kontrollen, nach denen ein Auditor den Deployer fragt: Es produziert die Audit-Trail-, Change-Management-, Daten-Lineage- und Supply-Chain-Nachweise auf jedem Lauf, bereit zur Aufnahme in Ihren Audit-Ordner.", @@ -4469,8 +4469,8 @@ "compliance.iso.cookbook": "Für das vollständige Audit-Cookbook, einschließlich 8 Audit-Floor-Fragen und einer 93-Kontrollen-Abdeckungskarte, siehe docs/guides/iso_soc2_deployer_guide.md.", "features.gov.heading": "Compliance- & DSGVO-Tooling", "features.gov.body": "EU-AI-Act-Artikel 9-17, DSGVO-Artikel 15 + 17, ISO-27001- / SOC-2-Type-II-Alignment-Nachweise.", - "features.gov.access.title": "DSGVO-Artikel-15-Auskunft", - "features.gov.access.body": "forgelm reverse-pii durchsucht maskierte JSONL-Korpora nach forensischen Substring- oder gesalzenen Digest-Treffern und bringt jede Zeile zum Vorschein, in der eine Betroffenenkennung erscheint.", + "features.gov.access.title": "Kennungssuche (Baustein für Art. 15)", + "features.gov.access.body": "forgelm reverse-pii durchsucht JSONL-Korpora nach Klartext- oder gesalzenen Digest-Treffern der Kennung einer betroffenen Person und listet die Zeilen der durchsuchten Dateien auf, die sie enthalten – ein Baustein der Auskunft nach Art. 15 DSGVO.", "features.gov.annex.title": "Annex-IV-Verifier", "features.gov.annex.body": "forgelm verify-annex-iv validiert die Annex-IV-§1-9-Schemavollständigkeit und den manifest_hash des Bundles zur Manipulationserkennung. (Audit-Ketten-Integrität ist der separate Befehl forgelm verify-audit.)", "features.gov.approve.title": "Artikel-14-Staging-Gate", @@ -4479,8 +4479,8 @@ "features.gov.gguf.body": "forgelm verify-gguf validiert den GGUF-Magic-Header, parst den Metadatenblock und prüft das beim Export geschriebene .sha256-Sidecar (sofern vorhanden) erneut, bevor Sie ein quantisiertes Modell ausliefern.", "features.gov.iso.title": "ISO 27001 / SOC 2 Alignment", "features.gov.iso.body": "Audit-Trail-, Change-Management-, Daten-Lineage- und Supply-Chain-Nachweise, nach denen der Auditor des Deployers fragt. Die Software ist aligned, nicht zertifiziert.", - "features.gov.purge.title": "DSGVO-Artikel-17-Löschung", - "features.gov.purge.body": "forgelm purge entfernt die Zeilen einer betroffenen Person aus Trainingskorpora und stempelt die Löschung in das Append-only-Audit-Log.", + "features.gov.purge.title": "Zeilenlöschung (Schritt für Art. 17)", + "features.gov.purge.body": "forgelm purge löscht Zeilen anhand ihrer ID aus einem Trainingskorpus und protokolliert die Löschung im Append-only-Audit-Log; das ist ein Schritt einer Löschung nach Art. 17 DSGVO (trainierte Gewichte bleiben unverändert).", "features.eval.safetyeval.title": "Standalone-Sicherheits-Eval", "features.eval.safetyeval.body": "forgelm safety-eval führt Llama Guard gegen eine --probes-JSONL-Prompt-Datei aus (oder --default-probes für das gebündelte 51-Prompt-Set über 18 Schadkategorien) und gibt eine kategorienspezifische Sicherheitszusammenfassung aus (safety_results.json + safety_trend.jsonl).", "features.ent.api.title": "Bibliotheks-API", @@ -4498,9 +4498,9 @@ /* ── Phase 22 / pass-4: backfill missing compliance + features keys (was H6 i18n gap) ── */ Object.assign(T.fr, { "compliance.gdpr15.title": "Droit d'accès", - "compliance.gdpr15.body": "forgelm reverse-pii scanne les corpus JSONL masqués à la recherche de résidus en clair ou de digests SHA-256 salés d'un identifiant de personne concernée et signale chaque ligne de chaque split où cette personne apparaît. Il ne trouve que ce qui figure littéralement dans les fichiers scannés : les sources brutes et le modèle entraîné ne sont pas parcourus, et le traitement d'une demande d'accès reste votre processus.", + "compliance.gdpr15.body": "forgelm reverse-pii parcourt des corpus JSONL à la recherche de l'identifiant d'une personne concernée, en clair ou sous forme d'empreinte SHA-256 salée écrite par un pipeline de masquage externe, et signale chaque ligne des fichiers analysés qui le contient. Par défaut, la recherche est littérale et sensible à la casse (--type custom prend une expression régulière). C'est un élément de votre réponse à une demande d'accès (article 15) ; cette réponse doit aussi fournir une copie des données et les informations visées à l'article 15, paragraphe 1, points a) à h). Les sources brutes et le modèle entraîné ne sont pas parcourus.", "compliance.gdpr17.title": "Droit à l'effacement", - "compliance.gdpr17.body": "forgelm purge supprime les lignes d'une personne concernée des corpus d'entraînement et estampille les événements data.erasure_requested + data.erasure_completed dans le log d'audit append-only, de sorte que les exécutions post-suppression soient liées de manière forensique à l'événement d'effacement.", + "compliance.gdpr17.body": "forgelm purge supprime des lignes d'un corpus d'entraînement d'après leur identifiant (id) et consigne les événements data.erasure_requested + data.erasure_completed dans le journal d'audit append-only. C'est une étape de l'effacement demandé au titre de l'article 17 : les poids entraînés, les documents sources et les copies conservées ailleurs ne sont pas modifiés.", "compliance.iso.title": "Aligné, non certifié", "compliance.iso.eyebrow": "ISO 27001 · SOC 2 Type II", "compliance.iso.lead": "Le logiciel ne peut pas être certifié ISO 27001 ou SOC 2 — seules les organisations le peuvent. ForgeLM est aligné avec les contrôles qu'un auditeur demande au déployeur : il produit les preuves d'audit-trail, de gestion du changement, de traçabilité des données et de supply chain à chaque exécution, prêtes à être déposées dans votre classeur d'audit.", @@ -4515,8 +4515,8 @@ "compliance.iso.cookbook": "Pour le cookbook d'audit complet, incluant 8 questions audit-floor et une cartographie de couverture de 93 contrôles, voir docs/guides/iso_soc2_deployer_guide.md.", "features.gov.heading": "Outillage Compliance & RGPD", "features.gov.body": "Articles 9-17 de l'EU AI Act, RGPD Articles 15 + 17, preuves d'alignement ISO 27001 / SOC 2 Type II.", - "features.gov.access.title": "Accès RGPD Article 15", - "features.gov.access.body": "forgelm reverse-pii scanne les corpus JSONL masqués à la recherche de correspondances de sous-chaînes forensiques ou de digests salés qui font remonter chaque ligne où apparaît un identifiant de personne concernée.", + "features.gov.access.title": "Recherche d'identifiant (élément de réponse, article 15)", + "features.gov.access.body": "forgelm reverse-pii parcourt des corpus JSONL à la recherche de l'identifiant d'une personne concernée, en clair ou sous forme d'empreinte salée, et liste les lignes des fichiers analysés qui le contiennent : un élément de la réponse à une demande d'accès (article 15).", "features.gov.annex.title": "Vérificateur Annex IV", "features.gov.annex.body": "forgelm verify-annex-iv valide la complétude du schéma Annex IV §1-9 et le manifest_hash du bundle pour la détection de falsification. (L'intégrité de la chaîne d'audit est la commande séparée forgelm verify-audit.)", "features.gov.approve.title": "Gate de staging Article 14", @@ -4525,8 +4525,8 @@ "features.gov.gguf.body": "forgelm verify-gguf valide l'en-tête magique GGUF, analyse le bloc de métadonnées et revérifie le fichier .sha256 écrit lors de l'export (s'il est présent) avant que vous n'expédiiez un modèle quantisé.", "features.gov.iso.title": "Alignement ISO 27001 / SOC 2", "features.gov.iso.body": "Preuves d'audit-trail, de gestion du changement, de traçabilité des données et de supply chain que l'auditeur du déployeur demande. Le logiciel est aligné, non certifié.", - "features.gov.purge.title": "Effacement RGPD Article 17", - "features.gov.purge.body": "forgelm purge supprime les lignes d'une personne concernée des corpus d'entraînement et estampille la suppression dans le log d'audit append-only.", + "features.gov.purge.title": "Effacement de lignes (étape de l'article 17)", + "features.gov.purge.body": "forgelm purge supprime des lignes d'un corpus d'entraînement d'après leur identifiant (id) et consigne l'effacement dans le journal d'audit append-only ; c'est une étape de l'effacement demandé au titre de l'article 17 (les poids entraînés ne changent pas).", "features.eval.safetyeval.title": "Évaluation sécurité autonome", "features.eval.safetyeval.body": "forgelm safety-eval exécute Llama Guard contre un fichier de prompts JSONL --probes (ou --default-probes pour le set bundlé de 51 prompts couvrant 18 catégories de préjudice) et émet un résumé de sécurité par catégorie (safety_results.json + safety_trend.jsonl).", "features.ent.api.title": "API bibliothèque", @@ -4544,9 +4544,9 @@ /* ── Phase 22 / pass-4: backfill missing compliance + features keys (was H6 i18n gap) ── */ Object.assign(T.es, { "compliance.gdpr15.title": "Derecho de acceso", - "compliance.gdpr15.body": "forgelm reverse-pii escanea corpus JSONL enmascarados en busca de residuos en texto claro o digests SHA-256 salados de un identificador de interesado y reporta cada fila de cada split donde aparece ese sujeto. Solo encuentra lo que figura literalmente en los archivos que escanea: no busca en las fuentes sin procesar ni en el modelo entrenado, y la gestión de una solicitud de acceso sigue siendo su proceso.", + "compliance.gdpr15.body": "forgelm reverse-pii escanea corpus JSONL en busca del identificador de un interesado, en texto claro o como hash SHA-256 con sal escrito por un pipeline de enmascaramiento externo, e indica cada línea de los archivos escaneados que lo contiene. Por defecto, la búsqueda es literal y distingue mayúsculas de minúsculas (--type custom acepta una expresión regular). Es un elemento de su respuesta a una solicitud de acceso (artículo 15); esa respuesta debe incluir además una copia de los datos y la información contemplada en el artículo 15, apartado 1, letras a) a h). No se examinan las fuentes sin procesar ni el modelo entrenado.", "compliance.gdpr17.title": "Derecho de supresión", - "compliance.gdpr17.body": "forgelm purge elimina las filas de un interesado de los corpus de entrenamiento y estampa eventos data.erasure_requested + data.erasure_completed en el log de auditoría append-only, de modo que las ejecuciones posteriores a la supresión queden vinculadas forensemente al evento de borrado.", + "compliance.gdpr17.body": "forgelm purge elimina filas por id de un corpus de entrenamiento y registra los eventos data.erasure_requested + data.erasure_completed en el log de auditoría append-only. Es un paso de la supresión solicitada conforme al artículo 17: los pesos entrenados, los documentos de origen y las copias que existan en otros lugares no cambian.", "compliance.iso.title": "Alineado, no certificado", "compliance.iso.eyebrow": "ISO 27001 · SOC 2 Type II", "compliance.iso.lead": "El software no puede ser certificado ISO 27001 o SOC 2 — solo las organizaciones pueden. ForgeLM está alineado con los controles que un auditor le pregunta al deployer: produce las pruebas de pista de auditoría, gestión de cambios, trazabilidad de datos y cadena de suministro en cada ejecución, listas para incorporarse a su carpeta de auditoría.", @@ -4561,8 +4561,8 @@ "compliance.iso.cookbook": "Para el cookbook de auditoría completo, incluidas 8 preguntas audit-floor y un mapa de cobertura de 93 controles, vea docs/guides/iso_soc2_deployer_guide.md.", "features.gov.heading": "Tooling Compliance & RGPD", "features.gov.body": "Artículos 9-17 del EU AI Act, RGPD Artículos 15 + 17, pruebas de alineamiento ISO 27001 / SOC 2 Type II.", - "features.gov.access.title": "Acceso RGPD Artículo 15", - "features.gov.access.body": "forgelm reverse-pii escanea corpus JSONL enmascarados en busca de coincidencias de subcadenas forenses o digests salados que sacan a la luz cada fila en la que aparece un identificador de interesado.", + "features.gov.access.title": "Búsqueda de identificador (elemento para el artículo 15)", + "features.gov.access.body": "forgelm reverse-pii escanea corpus JSONL en busca de coincidencias en texto claro o de hashes con sal del identificador de un interesado y lista las líneas de los archivos escaneados que lo contienen: un elemento de la respuesta a una solicitud de acceso (artículo 15).", "features.gov.annex.title": "Verificador Annex IV", "features.gov.annex.body": "forgelm verify-annex-iv valida la completitud del esquema Annex IV §1-9 y el manifest_hash del bundle para la detección de manipulación. (La integridad de la cadena de auditoría es el comando separado forgelm verify-audit.)", "features.gov.approve.title": "Gate de staging del Artículo 14", @@ -4571,8 +4571,8 @@ "features.gov.gguf.body": "forgelm verify-gguf valida la cabecera mágica GGUF, analiza el bloque de metadatos y reverifica el sidecar .sha256 escrito durante el export (si existe) antes de que envíe un modelo cuantizado.", "features.gov.iso.title": "Alineamiento ISO 27001 / SOC 2", "features.gov.iso.body": "Pruebas de pista de auditoría, gestión de cambios, trazabilidad de datos y cadena de suministro que pide el auditor del deployer. El software está alineado, no certificado.", - "features.gov.purge.title": "Supresión RGPD Artículo 17", - "features.gov.purge.body": "forgelm purge elimina las filas de un interesado de los corpus de entrenamiento y estampa la supresión en el log de auditoría append-only.", + "features.gov.purge.title": "Supresión de filas (paso del Artículo 17)", + "features.gov.purge.body": "forgelm purge elimina filas por id de un corpus de entrenamiento y registra la supresión en el log de auditoría append-only; es un paso de la supresión solicitada conforme al artículo 17 (los pesos entrenados no cambian).", "features.eval.safetyeval.title": "Evaluación de seguridad autónoma", "features.eval.safetyeval.body": "forgelm safety-eval ejecuta Llama Guard contra un archivo de prompts JSONL --probes (o --default-probes para el set bundleado de 51 prompts que cubre 18 categorías de daño) y emite un resumen de seguridad por categoría (safety_results.json + safety_trend.jsonl).", "features.ent.api.title": "API de biblioteca", @@ -4590,9 +4590,9 @@ /* ── Phase 22 / pass-4: backfill missing compliance + features keys (was H6 i18n gap) ── */ Object.assign(T.zh, { "compliance.gdpr15.title": "访问权", - "compliance.gdpr15.body": "forgelm reverse-pii 扫描已掩码的 JSONL 语料库,查找数据主体标识的明文残留或加盐 SHA-256 摘要,并报告该主体出现的每个 split 的每一行。它只能找到所扫描文件中字面存在的内容:不会搜索原始来源或已训练的模型,处理访问请求仍是你的流程。", + "compliance.gdpr15.body": "forgelm reverse-pii 扫描 JSONL 语料库,查找数据主体标识符的明文,或由外部脱敏流水线写入的该标识符的加盐 SHA-256 摘要,并报告所扫描文件中包含它的每一行。默认情况下匹配是逐字的,且区分大小写(--type custom 接受正则表达式)。其结果只是答复第 15 条访问请求的材料之一;该答复还必须提供个人数据副本以及第 15 条第 1 款第 (a) 至 (h) 项所列信息。不会搜索原始来源或已训练的模型。", "compliance.gdpr17.title": "删除权", - "compliance.gdpr17.body": "forgelm purge 从训练语料中删除某个数据主体的行,并在 append-only 审计日志中盖上 data.erasure_requested + data.erasure_completed 事件,使删除后的运行在取证上与该删除事件相连。", + "compliance.gdpr17.body": "forgelm purge 按 id 从训练语料中删除行,并将 data.erasure_requested + data.erasure_completed 事件记录到 append-only 审计日志。它只是第 17 条删除流程中的一步:已训练的权重、源文档和其他位置的副本不会改变。", "compliance.iso.title": "对齐,而非认证", "compliance.iso.eyebrow": "ISO 27001 · SOC 2 Type II", "compliance.iso.lead": "软件无法获得 ISO 27001 或 SOC 2 认证 — 只有组织可以。ForgeLM 与审计员向 deployer 询问的控制项保持对齐:它在每次运行时产出审计追踪、变更管理、数据血缘和供应链证据,可直接放入你的审计文件夹。", @@ -4607,8 +4607,8 @@ "compliance.iso.cookbook": "完整的审计 cookbook,包括 8 个 audit-floor 问题和 93 控制项覆盖图,见 docs/guides/iso_soc2_deployer_guide.md。", "features.gov.heading": "合规 & GDPR 工具", "features.gov.body": "EU AI Act 第 9-17 条、GDPR 第 15 + 17 条、ISO 27001 / SOC 2 Type II 对齐证据。", - "features.gov.access.title": "GDPR 第 15 条访问", - "features.gov.access.body": "forgelm reverse-pii 扫描已掩码的 JSONL 语料库,查找取证子串或加盐摘要匹配,使每一条数据主体标识出现的行浮现出来。", + "features.gov.access.title": "标识符查找(第 15 条答复材料之一)", + "features.gov.access.body": "forgelm reverse-pii 扫描 JSONL 语料库,查找数据主体标识符的明文或加盐摘要匹配,并列出所扫描文件中包含它的行,可作为答复第 15 条访问请求的材料之一。", "features.gov.annex.title": "Annex IV 验证器", "features.gov.annex.body": "forgelm verify-annex-iv 验证 Annex IV §1-9 模式完整性以及 bundle 的 manifest_hash 防篡改。(审计链完整性由独立的 forgelm verify-audit 命令负责。)", "features.gov.approve.title": "第 14 条 staging gate", @@ -4617,8 +4617,8 @@ "features.gov.gguf.body": "forgelm verify-gguf 在你交付量化模型之前,校验 GGUF 魔数头、解析 metadata 块,并(若存在)根据 export 时写入的 .sha256 sidecar 文件重新校验。", "features.gov.iso.title": "ISO 27001 / SOC 2 对齐", "features.gov.iso.body": "deployer 的审计员需要的审计追踪、变更管理、数据血缘和供应链证据。软件是对齐,而不是被认证。", - "features.gov.purge.title": "GDPR 第 17 条删除", - "features.gov.purge.body": "forgelm purge 从训练语料中移除某个数据主体的行,并将该删除盖入 append-only 审计日志。", + "features.gov.purge.title": "行删除 (第 17 条的其中一步)", + "features.gov.purge.body": "forgelm purge 按 id 从训练语料中删除行,并将该删除记录到 append-only 审计日志;它只是第 17 条删除流程中的一步(已训练的权重不会改变)。", "features.eval.safetyeval.title": "独立安全评估", "features.eval.safetyeval.body": "forgelm safety-eval 对 --probes JSONL 提示文件 (或用 --default-probes 调用打包的、覆盖 18 个危害类别的 51-提示集) 运行 Llama Guard,并产出按类别的安全摘要 (safety_results.json + safety_trend.jsonl)。", "features.ent.api.title": "库 API", diff --git a/site/js/wizard.js b/site/js/wizard.js index ca3dd6ab..08fe5b67 100644 --- a/site/js/wizard.js +++ b/site/js/wizard.js @@ -265,7 +265,7 @@ syntheticTeacherModel: 'gpt-4o', syntheticSeedFile: '', syntheticApiKeyEnv: 'OPENAI_API_KEY', - // GDPR Article 5+17 retention horizons. Defaults below match + // GDPR Article 5(1)(e) retention horizons. Defaults below match // forgelm/config.py RetentionConfig. retentionAuditLog: 1825, // 5 years retentionStaging: 7, @@ -3008,7 +3008,7 @@ } })); - // Retention horizons (GDPR Art. 5+17) accordion. + // Retention horizons (GDPR Art. 5(1)(e)) accordion. pane.appendChild(makeAccordion(state, persist, 'retention', 'wizard.retention.title', 'wizard.retention.hint', function (body) { function makeNumberRow(field, labelKey, hintKey) { diff --git a/site/privacy.html b/site/privacy.html index 8712c99a..948a3d95 100644 --- a/site/privacy.html +++ b/site/privacy.html @@ -129,7 +129,7 @@

Privacy policy