Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
08c26ff
docs(gdpr): stop presenting Article 17(3)(b) as a blanket audit-log d…
cemililik Oct 21, 2026
810c931
docs(gdpr): review round 1 — finish the retention and erasure wording
cemililik Oct 21, 2026
a61950c
docs(gdpr): review round 2 — purge and retention docs agree with the …
cemililik Oct 21, 2026
452c841
docs(pii): cite the GDPR articles that actually apply to the detector…
cemililik Oct 21, 2026
3428631
docs(gdpr): make the reverse-pii example runnable, point names to --p…
cemililik Oct 21, 2026
4102508
test(pii): ban the wrong GDPR attributions of the PII detector set br…
cemililik Oct 21, 2026
0d0ecfe
fix(docs-guard): flag unrecognised top-level YAML blocks in the user …
cemililik Oct 21, 2026
8bf2d08
docs(pii): the severity tiers are ForgeLM's weighting, and an identif…
cemililik Oct 21, 2026
5f4b846
docs(gdpr): review follow-up — the access/erasure walkthrough is cons…
cemililik Oct 21, 2026
3c8ec65
fix(docs-guard): review follow-up — multi-document YAML, language han…
cemililik Oct 21, 2026
2f33b05
docs(site): language review round 2 — the erasure card, "overdue", ar…
cemililik Oct 21, 2026
c2c3c79
fix: review round 2 — module-size budgets, enforce wording test, rete…
cemililik Oct 21, 2026
8282a51
docs: release note, agent guides and Phase 16 roadmap for the GDPR-cl…
cemililik Oct 21, 2026
14eb044
docs: CodeRabbit follow-ups — a CI gate that fails, digest/salt condi…
cemililik Oct 21, 2026
65b3305
fix(docs): digest-correlation rule, CI gate, exit codes, JSON envelop…
cemililik Oct 22, 2026
d1f0968
fix(docs): gpu-cost and chat pages, PII detector table, masking opt-i…
cemililik Oct 22, 2026
aaf63c4
fix(docs): retention wording, KVKK, human-oversight, QMS SOPs and des…
cemililik Oct 22, 2026
71215ad
test: pin the GDPR wording against the code, per sentence and per locale
cemililik Oct 22, 2026
ee07d96
fix(tools): harden the user-manual schema-drift guard
cemililik Oct 22, 2026
ced518f
fix(docs): lora target_modules, stale compliance.py line references, …
cemililik Oct 22, 2026
091303e
docs: changelog for the corrected GDPR documentation, sync-bilingual-…
cemililik Oct 22, 2026
7fceeaa
fix(docs): second verification pass — digest length, cost-recording g…
cemililik Oct 22, 2026
2107e9d
test: close the survivors of a 237-mutant review of the new tests and…
cemililik Oct 22, 2026
fd04f5d
fix(docs): artefacts caveat for generic run ids, response steps per r…
cemililik Oct 22, 2026
19c99e4
fix(docs): tie the zero-match caveat to --salt-source in the reverse-…
cemililik Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .agents/skills/sync-bilingual-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand Down
3 changes: 1 addition & 2 deletions .claude/skills/sync-bilingual-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand Down
16 changes: 9 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions config_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.<run_id>/ 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.<run_id>/ 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")
Expand Down
Loading
Loading