diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a95c500..b7d21be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -177,7 +177,7 @@ documentation pass. **Faz 22 — ISO 27001 / SOC 2 alignment design** - New design document at - [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) + [`docs/design/iso27001_soc2_alignment.md`](docs/design/iso27001_soc2_alignment.md) (~865 lines). Maps every ISO 27001:2022 Annex A control (93 controls across A.5 + A.6 + A.7 + A.8) and every SOC 2 Trust Services Criterion (Common Criteria CC1.x–CC9.x mandatory + 4 @@ -845,7 +845,7 @@ enough that the move can land alongside Phase 21's first commit. **Docs:** -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` §5.1 +- `docs/design/gdpr_erasure.md` §5.1 event catalog row for `data.erasure_requested` now lists `salt_source` (row mode only). Wave 2a Round-5 F-R5-05 introduced the field in §5.4 (per-event personal-data table) but did not propagate it to @@ -984,7 +984,7 @@ in this revision. Full delta: **Design-doc fixes** (no behavioural change to v0.5.5; tightens Phase 19 / 21 implementer guidance): -- Library API design (`library-api-design-202605021414.md`): CI trigger +- Library API design (`library_api.md`): CI trigger fixed (was `release-*` branch which release.md:259 forbids; now `pull_request` to main + `workflow_dispatch`); `[evaluation]` extra name fixed to `[eval]`; lazy-import test #9 promise corrected @@ -993,7 +993,7 @@ in this revision. Full delta: attribute access fixed to `["pairs"]` dict key access; tier table 10th test row added (`test_config_from_dict`); `__dir__()` recipe alignment; `forgelm.ingestion` correctly labelled "the module". -- GDPR erasure design (`gdpr-erasure-design-202605021414.md`): +- GDPR erasure design (`gdpr_erasure.md`): `staging_ttl_days` dual-set semantics resolved (ConfigError on conflict + tracking issue per release.md:95); §4.3 vs §10 Q5 contradiction resolved (`--check-policy` always exits 0; report-not- @@ -1059,7 +1059,7 @@ shipped a fix. Three were rejected with rationale: Fixed: -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` — +- `docs/design/gdpr_erasure.md` — unescaped `|` in markdown table cell (`compliance/*.json|yaml` → `compliance/*.{json,yaml}`); outdated hard-coded line count "445" in §12 sign-off replaced with a "recheck with `wc -l`" note that won't @@ -1153,17 +1153,17 @@ Fixed: - *qodo-code-review L682 ("doctor uses non-secret env-vars")* — same rejection as the round-1 entry above; doctor mirrors what `compliance.py::AuditLogger` and `huggingface_hub` upstream read. -- *CodeRabbit "Count erasure events consistently" (gdpr-erasure-design +- *CodeRabbit "Count erasure events consistently" (gdpr_erasure design L388 file map + L454 sign-off)* — file map already says six events at L389; the sign-off line at L454 says "six new audit events (three core erasure events + three operator-warning events)"; the L449 "+ 3 new warning events" parenthetical refers to what Round-2 *added* on top of Round-1's three core events, not the document's total. - *CodeRabbit "--check-policy exit code unambiguous"* — already - resolved at gdpr-erasure-design L196 (exit-code table cross-references + resolved at gdpr_erasure design L196 (exit-code table cross-references §10 Q5) + L199 (paragraph: "**`--check-policy` always exits 0**"). - *CodeRabbit "ForgeTrainer access must stay lazy"* — the test at - library-api-design L349 already pins lazy behaviour + library_api design L349 already pins lazy behaviour (`assert "torch" not in sys.modules` after `_ = forgelm.ForgeTrainer`); the bot misread it as the opposite. - *CodeRabbit TR installation L109 "`distributed` → `deepspeed` rename"* — @@ -1200,13 +1200,13 @@ Fixed: CI/CD scripts that branch on exit code. The `_SIGINT_GUARDED_SUBCOMMANDS` constant was deleted because the new policy is uniform across every subcommand. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.4 `target_id` row-mode source claim aligned with §4.2 — removed the "or line number" fallback from the table cell (it was already rejected at the CLI per L174); the cell now explicitly cites the Phase 28 `forgelm audit --add-row-ids` follow-up that operators with id-less corpora must run first. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §3.1 dual-set conflict-resolution bullets — varied the leading phrase across the four bullets ("When only X is set", "When only Y is set", "In the case where both are set with identical values", @@ -1214,10 +1214,10 @@ Fixed: prose-style lint stops flagging the four-in-a-row "If" pattern. Behaviour spec (alias-forward / canonical / DeprecationWarning / ConfigError) is unchanged. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §3.3 mtime-distrust sentence reworded to remove the "consumer that distrusts mtime" person/object ambiguity. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.3 wording: "by mistake" → "accidentally" for concision. - `docs/usermanuals/tr/reference/cli.md`: `--output DIR` row "ya da" → "veya" (more formal Turkish for the docs register); `forgelm @@ -1297,20 +1297,20 @@ OSError at ERROR level which `--quiet`'s WARNING floor surfaces). claimed the symbol was a re-export for tests. (F-R5-08) - `CHANGELOG.md` — inserted blank line before the `### Added — Wave 2a / Phase 34` heading (MD022 fix). (Inline A) -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.3 reconciled with §5.4: clarified that `target_id` handling depends on `target_kind` (run mode = clear, row mode = SHA-256 hash), and that the `row_id=42` examples in operator-facing prose show what the operator typed on the CLI rather than what lands in the audit chain. (Inline B) -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §3.4 reconciled with §4.1: `compliance/*.json` IS deletable via `forgelm purge --run-id --kind artefacts`; the previous "deleting them requires deleting the whole ``" claim contradicted the purge-mode table. Whole-`` deletion is now framed as the operator's manual escalation when the entire run is being scrubbed. (Inline C) -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` +- `docs/design/gdpr_erasure.md` §5.4 — `target_id` salt-fallback semantics tightened: the per-output-dir salt is **persistent** (written at `/.forgelm_audit_salt`, mode 0600, on first emission) @@ -1328,7 +1328,7 @@ OSError at ERROR level which `--quiet`'s WARNING floor surfaces). ### Added — Wave 2a — Phase 18 Library API design + Phase 20 GDPR erasure design - **Phase 18 — Library API analysis & design** — - `docs/analysis/code_reviews/library-api-design-202605021414.md` (525 lines). + `docs/design/library_api.md` (525 lines). 12 sections + 16-row task plan that pin the public Python surface for downstream consumers. Resolves the stable / experimental / internal tier split; documents the lazy-import invariant (`import forgelm` does not pull @@ -1340,7 +1340,7 @@ OSError at ERROR level which `--quiet`'s WARNING floor surfaces). `__getattr__` migration for `forgelm/cli/__init__.py`); specifies the `tests/test_library_api.py` integration suite Phase 19 must ship. - **Phase 20 — GDPR Article 17 erasure analysis & design** — - `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md`. 12 + `docs/design/gdpr_erasure.md`. 12 sections + 11-test plan + file map that pin the scope of Phase 21's `forgelm purge` implementation. Maps every Article 17(1) trigger to a ForgeLM action; enumerates the seven artefact kinds that may carry @@ -1590,7 +1590,7 @@ Both are design-only PRs — Phase 19 + Phase 21 implementations follow. [master-review-opus-202604300906.md](docs/analysis/code_reviews/master-review-opus-202604300906.md) (175 findings). - `data_audit/` + `cli/` package split design: - [split-design-data_audit-cli-202604300906.md](docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md) + [data_audit_cli_split.md](docs/design/data_audit_cli_split.md) (Faz 14-15 forward-looking). ### Deprecated @@ -1624,7 +1624,7 @@ Both are design-only PRs — Phase 19 + Phase 21 implementations follow. so external callers (`forgelm.ingestion`, `forgelm.wizard`) and the test suite keep working without code changes. Closes F-code-103 (Major). See - [split-design-data_audit-cli-202604300906.md](docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md) + [data_audit_cli_split.md](docs/design/data_audit_cli_split.md) §1 for the design. - **`forgelm/cli.py` → `forgelm/cli/` package (Faz 15)** — the ~2300-line monolith was split into a 24-module package (subcommands/, `_dispatch`, diff --git a/docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md b/docs/design/data_audit_cli_split.md similarity index 98% rename from docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md rename to docs/design/data_audit_cli_split.md index c76dfbd9..c7430526 100644 --- a/docs/analysis/code_reviews/split-design-data_audit-cli-202604300906.md +++ b/docs/design/data_audit_cli_split.md @@ -1,10 +1,15 @@ # Split Design — `forgelm/data_audit.py` and `forgelm/cli.py` -**Created:** 2026-04-30 -**Commit:** `6b515ed912f8f22304194c1b3f55ed07a26f519c` -**Branch:** `main` -**Companion to:** [`master-review-opus-202604300906.md`](./master-review-opus-202604300906.md) (§3 Theme γ, §5.5, §8.3) and [`closure-plan-202604300906.md`](./closure-plan-202604300906.md) -**Author:** Plan agent (read-only design pass; full file reads of both modules + every cross-import in `tests/` and `forgelm/`) +> **Scope:** Design specification for the sub-package split of two +> historically-monolithic modules: `forgelm/data_audit.py` (was 3098 +> lines) → `forgelm/data_audit/` package, and `forgelm/cli.py` (was +> 1756 lines) → `forgelm/cli/` package. Records the chosen module +> boundary, public-symbol re-export shape, and lazy-import contract +> rationale. Living spec — kept in sync with the implementation under +> `forgelm/data_audit/__init__.py` and `forgelm/cli/__init__.py`. + +**Status:** Implemented in v0.5.5 (Phase 14 + Phase 15 splits). See +`CHANGELOG.md` for the user-facing summary. --- diff --git a/docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md b/docs/design/gdpr_erasure.md similarity index 94% rename from docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md rename to docs/design/gdpr_erasure.md index 1b246a3b..3d63955e 100644 --- a/docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md +++ b/docs/design/gdpr_erasure.md @@ -1,12 +1,18 @@ -# ForgeLM GDPR Right-to-Erasure — Analysis & Design +# ForgeLM GDPR Right-to-Erasure — Design -**Document ID:** `gdpr-erasure-design-202605021414` -**Status:** Draft (Phase 20 deliverable) -**Author:** Closure Wave 2a -**Companion:** [`closure-plan-202604300906.md §8 Phase 20`](./closure-plan-202604300906.md) -**Implements (next phase):** Phase 21 — GDPR right-to-erasure implementation -**Base commit:** `0ffdfd6` (post-Wave-1 merge into `development`); Round-1 + Round-2 review fixes absorbed in the same wave (PR #28 `closure/wave2a-integration`). -**Regulatory anchor:** EU GDPR Article 17 ("right to erasure" / "right to be forgotten") + Article 5(1)(e) ("storage limitation") +> **Scope:** Design specification for ForgeLM's GDPR Article 17 +> right-to-erasure tool surface — the `forgelm purge` subcommand +> semantics, the `RetentionConfig` Pydantic schema, and the audit-chain +> integration that records each erasure / refusal event. Living spec — +> kept in sync with the implementation under +> `forgelm/cli/subcommands/_purge.py` and `forgelm/config.py` +> (`RetentionConfig`). + +**Regulatory anchor:** EU GDPR Article 17 ("right to erasure" / "right +to be forgotten") + Article 5(1)(e) ("storage limitation"). + +**Status:** Implemented in v0.5.5. See `CHANGELOG.md` and +`docs/guides/gdpr_erasure.md` for the user-facing surface. --- @@ -342,7 +348,7 @@ What ForgeLM does **not** do: - Pushing erasure notices to downstream processors (Article 17(2)) — 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 +See [`docs/guides/gdpr_erasure.md`](../guides/gdpr_erasure.md) for the operator how-to and the audit-event reference (Phase 21 deliverable; the file is added by the same PR that ships `forgelm purge`). ``` @@ -405,7 +411,7 @@ The closure plan §15.5 lists three open items that intersect Phase 21: |---|---| | **GH-023** — `ingestion.retention.raw_documents.ttl_days` config key cited by GDPR docs but not in `ForgeConfig` | Phase 21 adds `RetentionConfig` *as a top-level `retention:` block*, not nested under `ingestion`. The GDPR docs are updated to reference `retention.audit_log_retention_days` etc. An `ingestion.retention.*` alias is **not** added — the docs were ahead of the implementation, and the cleaner shape is to put retention under its own root key (it covers more than just ingestion artefacts). | | **GH-013** — `forgelm purge` documented but not implemented | Phase 21 ships exactly the form documented in `docs/usermanuals/en/compliance/gdpr-erasure.md` (`--row-id`, `--corpus`, `--run-id`, `--kind`, `--check-policy`). The user-manual page becomes accurate post-merge. | -| **Concurrent-approve race (Wave 1 round-5 carry-over)** | Phase 21 introduces `acquire_run_lock(run_id)` context manager (filelock pattern) for the audit-log + purge + approve coordination. Phase 22 ISO/SOC 2 scope already lists this; Phase 21 is the first place we actually need it (concurrent purge of the same row), so we ship the lock here and Phase 22 documents the broader policy. | +| **Concurrent-approve race** | Phase 21 introduces `acquire_run_lock(run_id)` context manager (filelock pattern) for the audit-log + purge + approve coordination. Phase 22 ISO/SOC 2 scope already lists this; Phase 21 is the first place we actually need it (concurrent purge of the same row), so we ship the lock here and Phase 22 documents the broader policy. | --- @@ -448,21 +454,12 @@ The closure plan §15.5 lists three open items that intersect Phase 21: --- -## 12. Sign-off checklist (Phase 20 acceptance) - -- [x] Document is ≥400 lines. (Concrete line count drifts as later review passes amend in place — recheck with `wc -l` rather than relying on a hard-coded count. Sections added through Round-1 + Round-2 review: §3.1 prior-state, §3.3 mtime caveat, §5.4 audit-PII minimisation, §4.4 commit-ordering recovery paths, scope-limitation paragraph + 3 new warning events.) -- [x] §1 maps every Article 17(1) trigger to ForgeLM scope. -- [x] §2 enumerates every artefact kind and its erasure strategy. -- [x] §3 specifies the `RetentionConfig` Pydantic schema (Phase 21 implements it verbatim). -- [x] §4 specifies the `forgelm purge` flag surface, exit codes, and atomicity guarantees. -- [x] §5 names the six new audit events (three core erasure events + three operator-warning events) + how chain integrity holds. -- [x] §6 supplies the marketing-claim replacement text. -- [x] §7 enumerates the 11 tests Phase 21 must ship (≥ closure-plan minimum 7). -- [x] §8 file map covers every change Phase 21 will make. -- [x] §9 absorbs the three relevant Wave 1 carry-overs (GH-023, GH-013, run-lock). -- [x] §10 records and resolves the six open questions. -- [x] §11 names what is deliberately out of scope. +## 12. Implementation status ---- +The design above shipped end-to-end in v0.5.5: -*End of Phase 20 design — `gdpr-erasure-design-202605021414`.* +- **`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`. +- **`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. diff --git a/docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md b/docs/design/iso27001_soc2_alignment.md similarity index 97% rename from docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md rename to docs/design/iso27001_soc2_alignment.md index f50c1913..4831554a 100644 --- a/docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md +++ b/docs/design/iso27001_soc2_alignment.md @@ -1,15 +1,13 @@ -# ISO 27001 / SOC 2 Type II — ForgeLM Alignment Design (Faz 22) +# ISO 27001 / SOC 2 Type II — ForgeLM Alignment Design -> Branch: `closure/wave4-integration` · Frozen: `2026-05-05 23:15` -> Closure-plan reference: [Faz 22](./closure-plan-202604300906.md#faz-22--iso-27001--soc-2-type-ii-alignment--analysis--gap-assessment-tek-pr-design-dosyası) -> Faz 22 acceptance bar: ≥800 satır design — see §13 acceptance criteria - -## Status - -**Wave 4 design pass.** Faz 22 ships a single deliverable: this document. Faz 23 -implements the gaps identified here (pip-audit + bandit CI, four new QMS docs, -deployer guide, two reference tables, README + CHANGELOG entries). Faz 26 picks -up QMS bilingual mirror obligations downstream of Faz 23. +> **Scope:** Deployer evidence map showing how ForgeLM artefacts +> (audit-trail, change-management, data-governance, encryption-at-rest, +> incident-response records) populate the ISO 27001 / SOC 2 Type II +> control surface a deployer faces during certification audit. Living +> spec — kept in sync with the implementation under +> `docs/qms/`, `docs/reference/iso27001_control_mapping.md`, +> `docs/reference/soc2_trust_criteria_mapping.md`, the pip-audit / +> bandit CI guards, and the QMS documents. **Important framing.** ForgeLM is a Python library + CLI. *Software cannot be ISO 27001 certified* — only **organisations** running an ISMS can. What @@ -20,8 +18,12 @@ requests is produced by ForgeLM as a byproduct of running the training pipeline. This document is the deployer's evidence map. The phrasing in customer-facing material is therefore **"ISO 27001 / SOC 2 -Type II alignment"** — never "certified" / "compliant". The README ISO/SOC 2 -section in Faz 23 follows this rule explicitly. +Type II alignment"** — never "certified" / "compliant". The +README ISO / SOC 2 section follows this rule explicitly. + +**Status:** Implemented in v0.5.5. See `CHANGELOG.md`, +`docs/guides/iso_soc2_deployer_guide.md`, and the QMS / reference +documents listed under §Scope above for the user-facing surface. ## 1. Background and scope @@ -355,7 +357,7 @@ ForgeLM is a single-node CLI; availability is dominantly deployer-side. | P2.1 Choice and consent | `evaluation.require_human_approval` Article 14 gate | | P3.1 Collection | `data.governance.personal_data_included` boolean; `dpia_completed` flag | | P3.2 Quality of personal data | `data_audit_report.json` quality stats | -| P4.1 Use, retention, and disposal | `evaluation.approval_retention_days` + `forgelm purge --check-policy` retention audit | +| P4.1 Use, retention, and disposal | `retention.staging_ttl_days` (canonical; legacy alias `evaluation.staging_ttl_days` forwards transparently during the v0.5.5 → v0.6.x deprecation window) + `forgelm purge --check-policy` retention audit | | P5.1 Access | `forgelm reverse-pii` Article 15 scan; salted query-hash in audit | | P5.2 Inquiries and complaints | (Deployer-side workflow) | | P6.1 Disclosure to third parties | `safe_post` webhook discipline; HMAC payload signing | @@ -859,12 +861,16 @@ code?* TSC source-of-truth. - [CycloneDX 1.5 specification](https://cyclonedx.org/specification/overview/) — SBOM format. -- [`docs/analysis/code_reviews/closure-plan-202604300906.md`](./closure-plan-202604300906.md) - — Faz 22 + Faz 23 source closure plan. -- [`docs/qms/`](../../qms/) — existing QMS templates (Wave 0). -- [`docs/reference/audit_event_catalog.md`](../../reference/audit_event_catalog.md) +- [`docs/qms/`](../qms/) — QMS templates (statement of applicability, + data management SOP, change management, incident response, key + management, retention). +- [`docs/reference/audit_event_catalog.md`](../reference/audit_event_catalog.md) — audit-event vocabulary. -- [`docs/standards/`](../../standards/) — engineering standards. +- [`docs/reference/iso27001_control_mapping.md`](../reference/iso27001_control_mapping.md) + — ISO/IEC 27001:2022 Annex A control → ForgeLM evidence map. +- [`docs/reference/soc2_trust_criteria_mapping.md`](../reference/soc2_trust_criteria_mapping.md) + — SOC 2 Type II Trust Services Criteria → ForgeLM evidence map. +- [`docs/standards/`](../standards/) — engineering standards. --- diff --git a/docs/analysis/code_reviews/library-api-design-202605021414.md b/docs/design/library_api.md similarity index 93% rename from docs/analysis/code_reviews/library-api-design-202605021414.md rename to docs/design/library_api.md index b96ba0c9..a4e28783 100644 --- a/docs/analysis/code_reviews/library-api-design-202605021414.md +++ b/docs/design/library_api.md @@ -1,11 +1,14 @@ -# ForgeLM Library API — Analysis & Design +# ForgeLM Library API — Design -**Document ID:** `library-api-design-202605021414` -**Status:** Draft (Phase 18 deliverable) -**Author:** Closure Wave 2a -**Companion:** [`closure-plan-202604300906.md §7 Phase 18`](./closure-plan-202604300906.md) -**Implements (next phase):** Phase 19 — Library API Implementation -**Base commit:** `0ffdfd6` (post-Wave-1 merge into `development`); Round-1 + Round-2 review fixes absorbed in the same wave (PR #28 `closure/wave2a-integration`). +> **Scope:** Design specification for ForgeLM as a Python library (the +> `from forgelm import ...` import surface). Defines stability tiers, +> lazy-import discipline, semver scope (`__api_version__`), and the +> documentation + test strategy for the library API. Living spec — +> kept in sync with the implementation under `forgelm/__init__.py`, +> `forgelm/_version.py`, and `tests/test_library_api.py`. + +**Status:** Implemented in v0.5.5. See `CHANGELOG.md` and +`docs/guides/library_api.md` for the user-facing surface. --- @@ -242,7 +245,7 @@ def __dir__() -> list[str]: return sorted({*globals().keys(), *_LAZY_SYMBOLS.keys()}) ``` -### 4.3 Wave 1 round-5 carry-over: `forgelm/cli/__init__.py` +### 4.3 CLI facade lazy migration: `forgelm/cli/__init__.py` The CLI facade currently has ~200 lines of eager `from .submodule import ...` calls (23 import statements pulling 50+ names; verified 2026-05-04 against `forgelm/cli/__init__.py`). That makes `import forgelm.cli` heavyweight even though most of those subcommand dispatchers are not needed for any single command. @@ -293,11 +296,11 @@ We add a regression test (`tests/test_cli_lazy_imports.py`) that: > **Status: Implementation diverged from this section.** The design doc > as drafted (Phase 18) committed to a 2-segment `__api_version__` (e.g. -> `"0.5"`). After drafting, [`docs/standards/release.md`](../../standards/release.md) +> `"0.5"`). After drafting, [`docs/standards/release.md`](../standards/release.md) > §"`__api_version__` (Python library surface)" (lines 27-39) codified > the canonical shape as **3-segment semver** (e.g. `"1.0.0"`) with > explicit MAJOR / MINOR / PATCH bump rules, and the implementation in -> [`forgelm/_version.py`](../../../forgelm/_version.py) lines 32-39 +> [`forgelm/_version.py`](../../forgelm/_version.py) lines 32-39 > follows the codified standard (`__api_version__ = "1.0.0"` at v0.5.5). > Defer to `release.md` for the public contract; the 2-segment shape > below is preserved here for historical context only. @@ -509,7 +512,7 @@ Things this design **does not** do, with the reason: | Out of scope | Why | |---|---| | Automatic `__api_version__` bumping in CI | Manual decision is the whole point — see Q4. | -| `forgelm.serve(...)` (HTTP wrapper) | Different product (Pro CLI dashboard); see [Phase 13 Pro CLI plan](../../roadmap/phase-13-pro-cli.md). | +| `forgelm.serve(...)` (HTTP wrapper) | Different product (Pro CLI dashboard); see [Phase 13 Pro CLI plan](../roadmap/phase-13-pro-cli.md). | | `mypy --strict` on the whole codebase | See Q3. | | Automatic `library_api.md` generation | See Q2. | | Async variants (`async def train(...)`) | Trainer is fundamentally synchronous (TRL holds GPU state). An async wrapper would be a different module. | @@ -517,21 +520,14 @@ Things this design **does not** do, with the reason: --- -## 12. Sign-off checklist (Phase 18 acceptance) - -- [x] Document is ≥400 lines. (Line count ≈ 470 incl. tables.) -- [x] Every stable-tier symbol from §2.1 has an explicit signature snippet **or** is referenced by a stable-tier file we already ship. *(Audited 2026-05-02: zero existing tests do `from forgelm import X` for any of the new stable names — that gap is exactly what Phase 19 task #7 closes. The §2.1 listing is the design promise; Phase 19 ships the regression coverage.)* -- [x] §3 names a CI step + sample command for type-hint enforcement. -- [x] §4 documents the existing lazy-import invariant + names the regression test that pins it. -- [x] §4.3 explicitly resolves the Wave 1 round-5 carry-over (CLI facade lazy migration). -- [x] §5 names two version strings + their semantics + the deprecation cadence. -- [x] §6 enumerates the integration test cases Phase 19 must implement. -- [x] §7 lists every documentation deliverable Phase 19 must produce. -- [x] §8 covers logging, config-from-dict, errors, and concurrency. -- [x] §9 is a 16-row task plan precise enough to execute Phase 19 from this document alone. -- [x] §10 records and resolves the five open questions. -- [x] §11 names what is deliberately out of scope. +## 12. Implementation status ---- +The design above shipped end-to-end in v0.5.5: -*End of Phase 18 design — `library-api-design-202605021414`.* +- Stable / Experimental / Internal tiers (§2): pinned in `forgelm/__init__.py::__all__` + `_LAZY_SYMBOLS`. +- Type-hint surface (§3): `py.typed` marker ships in the wheel; public functions carry annotations. +- Lazy-import discipline (§4): `forgelm/__init__.py::__getattr__` resolves on first access; `forgelm/cli/__init__.py` mirrors the pattern; regression test corpus at `tests/test_lazy_imports.py` covers `forgelm.trainer`, `model`, `data`, `benchmark`, `safety`, `inference`, `synthetic`, `judge` (no eager torch / transformers / trl / datasets). +- Two version strings (§5): `__version__` (runtime, `importlib.metadata`-derived) + `__api_version__` (semver `1.0.0`); both live in `forgelm/_version.py`. +- Integration tests (§6): `tests/test_library_api.py` covers public-surface invariants — stable-symbol roster matches `forgelm.__all__`, lazy-import discipline (subprocess assertion that `import forgelm` does not pull `torch` / `transformers` / `trl`), `__getattr__` resolution + caching, `dir(forgelm)` shape, `py.typed` marker presence, and `__api_version__` contract — paired with `tests/test_lazy_imports.py` for the per-submodule lazy-load regression corpus. End-to-end train-and-chat user journeys are exercised by the per-trainer test modules (`tests/test_trainer*.py`, `tests/test_chat.py`), not by `test_library_api.py`. +- Docs (§7): `docs/reference/library_api_reference{,-tr}.md`, `docs/guides/library_api{,-tr}.md`, `docs/usermanuals/{en,tr}/reference/library-api.md`, README "Library API" section. +- Errors + concurrency (§8): `ConfigError`, `OptionalDependencyError`, `HttpSafetyError`; multiprocess-safe `AuditLogger.log_event` via `fcntl.flock` / `msvcrt.locking`. diff --git a/docs/guides/gdpr_erasure-tr.md b/docs/guides/gdpr_erasure-tr.md index 089c6343..63609bfa 100644 --- a/docs/guides/gdpr_erasure-tr.md +++ b/docs/guides/gdpr_erasure-tr.md @@ -186,4 +186,4 @@ glob); `2` = runtime hatası (mid-scan I/O failure). JSON envelope - `docs/qms/sop_data_management.md` — retention + erasure prosedürleri dahil tam data-lifecycle SOP. - `docs/usermanuals/tr/compliance/safety_compliance.md` — buraya link veren operator-facing compliance overview. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` — bu implementation'ın gerçekleştirdiği Phase 20 design dokümanı. +- [`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 4b496be7..2d82ef93 100644 --- a/docs/guides/gdpr_erasure.md +++ b/docs/guides/gdpr_erasure.md @@ -185,4 +185,4 @@ for the JSON envelope schema. - `docs/qms/sop_data_management.md` — the full data-lifecycle SOP including the retention + erasure procedures. - `docs/usermanuals/en/compliance/safety_compliance.md` — the operator-facing compliance overview that links here. -- `docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md` — the Phase 20 design document that this implementation realises. +- [`docs/design/gdpr_erasure.md`](../design/gdpr_erasure.md) — the Phase 20 design document that this implementation realises. diff --git a/docs/guides/iso_soc2_deployer_guide-tr.md b/docs/guides/iso_soc2_deployer_guide-tr.md index 3f23a3b6..6d657cbc 100644 --- a/docs/guides/iso_soc2_deployer_guide-tr.md +++ b/docs/guides/iso_soc2_deployer_guide-tr.md @@ -12,7 +12,7 @@ > ForgeLM artefaktının cevap verdiğini gösterir. > > Çapraz referans: -> [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> [`docs/design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > tam tasarım gerekçesi + 93 kontrollü kapsama haritası için. ## ForgeLM'in size out-of-the-box sağladıkları @@ -279,7 +279,7 @@ Operatörlerin ilk denetimlerinde yanlış yaptığı şeyler: ## Bkz. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — tam tasarım gerekçesi + 93 kontrol haritası. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — tam tasarım gerekçesi + 93 kontrol haritası. - [`../qms/encryption_at_rest-tr.md`](../qms/encryption_at_rest-tr.md) — substrate-side şifreleme rehberi. - [`../qms/access_control-tr.md`](../qms/access_control-tr.md) — operator identity + secrets management. - [`../qms/risk_treatment_plan-tr.md`](../qms/risk_treatment_plan-tr.md) — pre-populated risk register. diff --git a/docs/guides/iso_soc2_deployer_guide.md b/docs/guides/iso_soc2_deployer_guide.md index a1fdb80a..ffbeb029 100644 --- a/docs/guides/iso_soc2_deployer_guide.md +++ b/docs/guides/iso_soc2_deployer_guide.md @@ -12,7 +12,7 @@ > ForgeLM artefact answers it. > > Cross-reference: -> [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> [`docs/design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > for the full design rationale + 93-control coverage map. ## What ForgeLM gives you out-of-the-box @@ -280,7 +280,7 @@ Things deployers get wrong on their first audit: ## References -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — full design rationale + 93-control coverage map. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — full design rationale + 93-control coverage map. - [`../qms/encryption_at_rest.md`](../qms/encryption_at_rest.md) — substrate-side encryption guidance. - [`../qms/access_control.md`](../qms/access_control.md) — operator identity + secrets management. - [`../qms/risk_treatment_plan.md`](../qms/risk_treatment_plan.md) — pre-populated risk register. diff --git a/docs/guides/library_api-tr.md b/docs/guides/library_api-tr.md index 1dc5f3df..559bf829 100644 --- a/docs/guides/library_api-tr.md +++ b/docs/guides/library_api-tr.md @@ -4,7 +4,7 @@ > > **Eşlik eden referans:** [`../reference/library_api_reference-tr.md`](../reference/library_api_reference-tr.md) — her public sembol, imza ve kararlılık katmanı. > -> **Tasarım kaynağı:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Faz 18). +> **Tasarım kaynağı:** [`../design/library_api.md`](../design/library_api.md) (Faz 18). ForgeLM **eşit derecede first-class iki giriş noktası** yayınlar: shell pipeline'ları için `forgelm` console script'i (ve `python -m forgelm.cli`), programatik kullanım için burada belgelenen Python kütüphane API'si. CLI sarmalayıcıdır; kütüphane motordur. CLI'nin yaptığı her şeyi kütüphane yapabilir — exit-code eşlemesi ve yapılandırılmış logging kurulumu hariç. @@ -267,4 +267,4 @@ Library API ve CLI aynı wheel'i, aynı audit log'u ve aynı lock semantik'ini p - [`../reference/configuration-tr.md`](../reference/configuration-tr.md) — `ForgeConfig` alan-by-alan referans. - [`cicd_pipeline.md`](cicd_pipeline.md) — bu rehberin CLI karşılığı. - [`iso_soc2_deployer_guide-tr.md`](iso_soc2_deployer_guide-tr.md) — audit katı cookbook'u (kütüphane çağıranları aynı artefaktları görür). -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Faz 18 tasarım. +- [`../design/library_api.md`](../design/library_api.md) — Faz 18 tasarım. diff --git a/docs/guides/library_api.md b/docs/guides/library_api.md index 0636273d..3a800e7a 100644 --- a/docs/guides/library_api.md +++ b/docs/guides/library_api.md @@ -4,7 +4,7 @@ > > **Companion reference:** [`../reference/library_api_reference.md`](../reference/library_api_reference.md) — every public symbol, signature, and stability tier. > -> **Design source:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Phase 18). +> **Design source:** [`../design/library_api.md`](../design/library_api.md) (Phase 18). ForgeLM ships **two equally first-class entry points**: the `forgelm` console script (and `python -m forgelm.cli`) for shell pipelines, and the Python library API documented here for programmatic use. The CLI is the wrapper; the library is the engine. Anything the CLI does, the library can do — minus the exit-code mapping and the structured logging setup. @@ -266,4 +266,4 @@ The library API and the CLI share the same wheel, the same audit log, and the sa - [`../reference/configuration.md`](../reference/configuration.md) — `ForgeConfig` field-by-field reference. - [`cicd_pipeline.md`](cicd_pipeline.md) — the CLI counterpart of this guide. - [`iso_soc2_deployer_guide.md`](iso_soc2_deployer_guide.md) — audit-floor cookbook (library callers see the same artefacts). -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Phase 18 design. +- [`../design/library_api.md`](../design/library_api.md) — Phase 18 design. diff --git a/docs/qms/statement_of_applicability-tr.md b/docs/qms/statement_of_applicability-tr.md index 5e7148bc..486ceb5f 100644 --- a/docs/qms/statement_of_applicability-tr.md +++ b/docs/qms/statement_of_applicability-tr.md @@ -2,7 +2,7 @@ > Kalite Yönetim Sistemi — [YOUR ORGANIZATION] > ISO 27001:2022 Annex A — clause 6.1.3 d) tarafından gerekli -> Çapraz referans: [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> Çapraz referans: [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > tam mapping gerekçesi için. ## 1. Amaç @@ -156,7 +156,7 @@ Yukarıdaki §2.1–§2.4 (SoA matrisi) row-by-row yeniden sayım. Tema başına tally — A.5: 3 / 24 / 10 OOS; A.6: 0 / 5 / 3 OOS; A.7: 0 / 0 / 14 OOS; A.8: 8 / 19 / 7 OOS — toplam 11 `FL` + 48 `FL-helps` + 34 OOS = 93. Design doc'un §3 "Coverage tally" paragrafıyla -(`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`) +(`docs/design/iso27001_soc2_alignment.md`) çapraz kontrol; ikisi eşleşmek zorunda. ## 4. İnceleme diff --git a/docs/qms/statement_of_applicability.md b/docs/qms/statement_of_applicability.md index eef5fbfe..a6c7e22b 100644 --- a/docs/qms/statement_of_applicability.md +++ b/docs/qms/statement_of_applicability.md @@ -2,7 +2,7 @@ > Quality Management System — [YOUR ORGANIZATION] > ISO 27001:2022 Annex A — required by clause 6.1.3 d) -> Cross-reference: [`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +> Cross-reference: [`docs/design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) > for the full mapping rationale. ## 1. Purpose @@ -155,7 +155,7 @@ Row-by-row recount of §2.1–§2.4 above (the SoA matrix). Per-theme tally — A.5: 3 / 24 / 10 OOS; A.6: 0 / 5 / 3 OOS; A.7: 0 / 0 / 14 OOS; A.8: 8 / 19 / 7 OOS — sums to 11 `FL` + 48 `FL-helps` + 34 OOS = 93. Cross-check the design doc's §3 "Coverage tally" paragraph -(`docs/analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`); +(`docs/design/iso27001_soc2_alignment.md`); the two must match. ## 4. Review diff --git a/docs/reference/compliance_summary.md b/docs/reference/compliance_summary.md index b0c71ead..4e2e9aa9 100644 --- a/docs/reference/compliance_summary.md +++ b/docs/reference/compliance_summary.md @@ -122,7 +122,7 @@ What the regulator asks vs. how ForgeLM answers: ### ISO 27001 / SOC 2 Type II alignment (Wave 4) -- Design doc: [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) +- Design doc: [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) (~865 lines, full 93-control coverage map). - Deployer cookbook: [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md). - Reference tables: [`iso27001_control_mapping.md`](iso27001_control_mapping.md), diff --git a/docs/reference/iso27001_control_mapping-tr.md b/docs/reference/iso27001_control_mapping-tr.md index 50c89b82..a126ae26 100644 --- a/docs/reference/iso27001_control_mapping-tr.md +++ b/docs/reference/iso27001_control_mapping-tr.md @@ -4,7 +4,7 @@ > eşlendiğini özetleyen referans tablo. Şu belgelere eşlik eder: > [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) > ve tasarım dökümanı -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). > > **Coverage tier legend:** > @@ -136,7 +136,7 @@ SoA'sının uçtan uca auditable olması için tamlık adına listelenmiştir. ## Bkz. - [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) — operatör denetim cookbook'u. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — tam tasarım gerekçesi. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — tam tasarım gerekçesi. - [`soc2_trust_criteria_mapping-tr.md`](soc2_trust_criteria_mapping-tr.md) — SOC 2 mapping eşlikçisi. - [`supply_chain_security-tr.md`](supply_chain_security-tr.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog-tr.md`](audit_event_catalog-tr.md) — audit-event vocabulary. diff --git a/docs/reference/iso27001_control_mapping.md b/docs/reference/iso27001_control_mapping.md index fce0b2e3..7e43b110 100644 --- a/docs/reference/iso27001_control_mapping.md +++ b/docs/reference/iso27001_control_mapping.md @@ -4,7 +4,7 @@ > Annex A controls. Companion to > [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) > and the design doc -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). > > **Coverage tier legend:** > @@ -137,7 +137,7 @@ completeness so the deployer's SoA is auditable end-to-end. ## See also - [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) — deployer audit cookbook. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — full design rationale. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — full design rationale. - [`soc2_trust_criteria_mapping.md`](soc2_trust_criteria_mapping.md) — SOC 2 mapping companion. - [`supply_chain_security.md`](supply_chain_security.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog.md`](audit_event_catalog.md) — audit-event vocabulary. diff --git a/docs/reference/library_api_reference-tr.md b/docs/reference/library_api_reference-tr.md index 8381a755..0b32a94e 100644 --- a/docs/reference/library_api_reference-tr.md +++ b/docs/reference/library_api_reference-tr.md @@ -6,7 +6,7 @@ > > **Eşlik eden rehber:** [`../guides/library_api-tr.md`](../guides/library_api-tr.md) — uçtan uca üç işlenmiş örnek. > -> **Tasarım kaynağı:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Faz 18). +> **Tasarım kaynağı:** [`../design/library_api.md`](../design/library_api.md) (Faz 18). ForgeLM, `forgelm` console script'inin yanında bir Python kütüphane API'si de yayınlar. Kütüphane yüzeyi `forgelm/__init__.py` içinde `__all__` ile beyan edilir, PEP 562 `__getattr__` üzerinden lazy-resolve edilir ve downstream `mypy --strict` tüketicilerin gerçek imzaları görmesi için `TYPE_CHECKING` altında tip ipucu ile işaretlenir. `forgelm/py.typed` PEP 561 işaretçisi olarak wheel ile birlikte yayınlanır. @@ -16,7 +16,7 @@ ForgeLM, `forgelm` console script'inin yanında bir Python kütüphane API'si de ### Stable -Semver korumalıdır. Aşağıdaki imzalardan herhangi birinde kırıcı değişiklik major sürüm artışı gerektirir (`__api_version__` MAJOR.MINOR — bkz. [Sürümleme ve deprecation politikası](#sürümleme-ve-deprecation-politikası)). Varsayılan değerli yeni opsiyonel parametreler kırıcı değildir; yeniden adlandırılmış zorunlu parametreler veya kaldırılmış return-shape alanları kırıcıdır. +Semver korumalıdır. Aşağıdaki imzalardan herhangi birinde kırıcı değişiklik major sürüm artışı gerektirir (`__api_version__` MAJOR.MINOR.PATCH — bkz. [Sürümleme ve deprecation politikası](#sürümleme-ve-deprecation-politikası)). Varsayılan değerli yeni opsiyonel parametreler kırıcı değildir; yeniden adlandırılmış zorunlu parametreler veya kaldırılmış return-shape alanları kırıcıdır. Stable semboller burada belgelenir, %100 tip ipuçludur, `tests/test_library_api.py` altında en az bir entegrasyon testi vardır ve deprecation cadence'ini izler (sürüm `N`'de deprecate, `N+1`'de çalışır halde tut, `N+2`'de kaldır). @@ -37,7 +37,7 @@ Best-effort. Şekil, major artış olmadan minor sürümde değişebilir. Çağr | Sembol | Katman | Tip | Açıklama | |---|---|---|---| | `forgelm.__version__` | Stable | `str` | PEP 396/8 sürüm dizesi, `importlib.metadata`'dan türetilir (tek doğru kaynak = `pyproject.toml`). | -| `forgelm.__api_version__` | Stable | `str` | İki segmentli kütüphane-API sürümü (`"MAJOR.MINOR"`). Yalnızca stable katman imzası değiştiğinde artar. Downstream kodda feature detection için kullanın. | +| `forgelm.__api_version__` | Stable | `str` | Üç segmentli semver kütüphane-API sürümü (`"MAJOR.MINOR.PATCH"`, örn. `"1.0.0"`). Bump kuralları `forgelm/_version.py`'de: kaldırılan/imzası değişen stable sembol için MAJOR, yeni stable sembol için MINOR, public yüzeye dokunmayan implementasyon değişikliği için PATCH. Downstream kodda feature detection için kullanın. | ### Konfigürasyon @@ -274,7 +274,7 @@ CLI kendi kurulumunu `forgelm.cli._setup_logging` içinde yapar; kütüphane kar | `forgelm.__version__` | Her sürüm (CLI fix, kütüphane fix, doc-only release) | Downstream pinning, audit manifest stamp | | `forgelm.__api_version__` | Bir stable katman imzası değiştiğinde | Downstream feature detection | -`__api_version__` iki segmentli bir string (`"0.5"`); kütüphaneye yapılan patch seviyesindeki değişiklikler tanım gereği kırıcı değildir, dolayısıyla hiçbir tüketicinin bunu tespit etmeye ihtiyacı yoktur. +`__api_version__` üç segmentli bir semver string'idir (`"1.0.0"` v0.5.5'te — formel Phase 19 kontratının ilk yayını). Bump kuralları `forgelm/_version.py`'de: kaldırılan / imzası değişen stable sembol için MAJOR, yeni stable sembol için MINOR, public yüzeye dokunmayan implementasyon değişikliği için PATCH. **Deprecation cadence** (`docs/standards/release.md` başına): @@ -289,5 +289,5 @@ Cadence'i izlemeden stable bir imzaya kırıcı değişiklik yapmak release-proc - [`../guides/library_api-tr.md`](../guides/library_api-tr.md) — uçtan uca üç işlenmiş örnek. - [`audit_event_catalog-tr.md`](audit_event_catalog-tr.md) — `AuditLogger.log_event`'in kabul ettiği tam olay kelime dağarcığı. - [`configuration-tr.md`](configuration-tr.md) — `ForgeConfig` alan referansı. -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Faz 18 tasarım + 16 satırlık Faz 19 görev planı. +- [`../design/library_api.md`](../design/library_api.md) — Faz 18 tasarım + 16 satırlık Faz 19 görev planı. - [`../standards/release.md`](../standards/release.md) — deprecation cadence ve sürüm süreci. diff --git a/docs/reference/library_api_reference.md b/docs/reference/library_api_reference.md index a4970f17..f7fbcebd 100644 --- a/docs/reference/library_api_reference.md +++ b/docs/reference/library_api_reference.md @@ -6,7 +6,7 @@ > > **Companion guide:** [`../guides/library_api.md`](../guides/library_api.md) — three end-to-end worked examples. > -> **Design source:** [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) (Phase 18). +> **Design source:** [`../design/library_api.md`](../design/library_api.md) (Phase 18). ForgeLM ships a Python library API alongside the `forgelm` console script. The library surface is declared in `forgelm/__init__.py` via `__all__`, lazy-resolved through PEP 562 `__getattr__`, and type-hinted under `TYPE_CHECKING` so downstream `mypy --strict` consumers see real signatures. `forgelm/py.typed` ships in the wheel as the PEP 561 marker. @@ -16,7 +16,7 @@ Three tiers govern the semver weight of every public symbol. A consumer that pin ### Stable -Semver-protected. A breaking change to any signature below requires a major version bump (`__api_version__` MAJOR.MINOR — see [Versioning and deprecation policy](#versioning-and-deprecation-policy)). New optional parameters with defaults are non-breaking; renamed required parameters or removed return-shape fields are breaking. +Semver-protected. A breaking change to any signature below requires a major version bump (`__api_version__` MAJOR.MINOR.PATCH — see [Versioning and deprecation policy](#versioning-and-deprecation-policy)). New optional parameters with defaults are non-breaking; renamed required parameters or removed return-shape fields are breaking. Stable symbols are documented here, are 100% type-hinted, have at least one integration test under `tests/test_library_api.py`, and follow the deprecation cadence (deprecate in `N`, keep working in `N+1`, remove in `N+2`). @@ -37,7 +37,7 @@ Tables grouped by concern. Every cell is a real attribute on the live `forgelm` | Symbol | Tier | Type | Description | |---|---|---|---| | `forgelm.__version__` | Stable | `str` | PEP 396/8 release version, derived from `importlib.metadata` (single source of truth = `pyproject.toml`). | -| `forgelm.__api_version__` | Stable | `str` | Two-segment library-API version (`"MAJOR.MINOR"`). Bumped only when a stable-tier signature changes. Use for feature detection in downstream code. | +| `forgelm.__api_version__` | Stable | `str` | Three-segment semver library-API version (`"MAJOR.MINOR.PATCH"`, e.g. `"1.0.0"`). Bumped per the rules in `forgelm/_version.py`: MAJOR for removed/signature-changed stable symbols, MINOR for new stable symbols, PATCH for implementation-only changes. Use for feature detection in downstream code. | ### Configuration @@ -274,7 +274,7 @@ Two independent version strings: | `forgelm.__version__` | Every release (CLI fix, library fix, doc-only release) | Downstream pinning, audit manifest stamp | | `forgelm.__api_version__` | A stable-tier signature changes | Downstream feature detection | -`__api_version__` is a two-segment string (`"0.5"`); patch-level changes to the library are by definition non-breaking, so no consumer needs to detect them. +`__api_version__` is a three-segment semver string (`"1.0.0"` at v0.5.5 — first publication of the formal Phase 19 contract). Bump rules live in `forgelm/_version.py`: MAJOR on removed / signature-changed stable symbols, MINOR on new stable symbols, PATCH on implementation-only changes that don't touch the public surface. **Deprecation cadence** (per `docs/standards/release.md`): @@ -289,5 +289,5 @@ A breaking change to a stable signature without following the cadence is a relea - [`../guides/library_api.md`](../guides/library_api.md) — three end-to-end worked examples. - [`audit_event_catalog.md`](audit_event_catalog.md) — full event vocabulary `AuditLogger.log_event` accepts. - [`configuration.md`](configuration.md) — `ForgeConfig` field reference. -- [`../analysis/code_reviews/library-api-design-202605021414.md`](../analysis/code_reviews/library-api-design-202605021414.md) — Phase 18 design + 16-row Phase 19 task plan. +- [`../design/library_api.md`](../design/library_api.md) — Phase 18 design + 16-row Phase 19 task plan. - [`../standards/release.md`](../standards/release.md) — deprecation cadence and release process. diff --git a/docs/reference/soc2_trust_criteria_mapping-tr.md b/docs/reference/soc2_trust_criteria_mapping-tr.md index 426156e9..c00e1947 100644 --- a/docs/reference/soc2_trust_criteria_mapping-tr.md +++ b/docs/reference/soc2_trust_criteria_mapping-tr.md @@ -5,7 +5,7 @@ > tablo. Şu belgelere eşlik eder: > [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) > ve tasarım dökümanı -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). ## Kategoriler @@ -96,7 +96,7 @@ Güçlü ForgeLM katkısı. | P2.1 Seçim ve onay | `evaluation.require_human_approval` Madde 14 gate | | P3.1 Toplama | `data.governance.personal_data_included`; `dpia_completed` | | P3.2 Kişisel veri kalitesi | `data_audit_report.json` quality stats | -| P4.1 Kullanım, saklama ve imha | `evaluation.approval_retention_days`; `forgelm purge --check-policy` | +| P4.1 Kullanım, saklama ve imha | `retention.staging_ttl_days` (kanonik; eski takma ad `evaluation.staging_ttl_days` v0.5.5 → v0.6.x deprecation penceresi boyunca şeffaf yönlendirir); `forgelm purge --check-policy` | | P5.1 Erişim | `forgelm reverse-pii` Madde 15 scan; salted query-hash | | P5.2 Sorular ve şikayetler | (Operatör-tarafı workflow) | | P6.1 Üçüncü taraflara açıklama | `safe_post` webhook discipline; HMAC payload signing | @@ -123,7 +123,7 @@ Güçlü ForgeLM katkısı. ## Bkz. - [`../guides/iso_soc2_deployer_guide-tr.md`](../guides/iso_soc2_deployer_guide-tr.md) — operatör denetim cookbook'u. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — tam tasarım gerekçesi. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — tam tasarım gerekçesi. - [`iso27001_control_mapping-tr.md`](iso27001_control_mapping-tr.md) — ISO 27001 mapping eşlikçisi. - [`supply_chain_security-tr.md`](supply_chain_security-tr.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog-tr.md`](audit_event_catalog-tr.md) — audit-event vocabulary. diff --git a/docs/reference/soc2_trust_criteria_mapping.md b/docs/reference/soc2_trust_criteria_mapping.md index d2c0d627..0c8c32a2 100644 --- a/docs/reference/soc2_trust_criteria_mapping.md +++ b/docs/reference/soc2_trust_criteria_mapping.md @@ -5,7 +5,7 @@ > Companion to > [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) > and the design doc -> [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md). +> [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md). ## Categories @@ -97,7 +97,7 @@ Strong ForgeLM contribution. | P2.1 Choice and consent | `evaluation.require_human_approval` Article 14 gate | | P3.1 Collection | `data.governance.personal_data_included`; `dpia_completed` | | P3.2 Quality of personal data | `data_audit_report.json` quality stats | -| P4.1 Use, retention, and disposal | `evaluation.approval_retention_days`; `forgelm purge --check-policy` | +| P4.1 Use, retention, and disposal | `retention.staging_ttl_days` (canonical; legacy alias `evaluation.staging_ttl_days` forwards transparently during the v0.5.5 → v0.6.x deprecation window); `forgelm purge --check-policy` | | P5.1 Access | `forgelm reverse-pii` Article 15 scan; salted query-hash | | P5.2 Inquiries and complaints | (Deployer-side workflow) | | P6.1 Disclosure to third parties | `safe_post` webhook discipline; HMAC payload signing | @@ -124,7 +124,7 @@ Strong ForgeLM contribution. ## See also - [`../guides/iso_soc2_deployer_guide.md`](../guides/iso_soc2_deployer_guide.md) — deployer audit cookbook. -- [`../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md`](../analysis/code_reviews/iso27001-soc2-alignment-202605052315.md) — full design rationale. +- [`../design/iso27001_soc2_alignment.md`](../design/iso27001_soc2_alignment.md) — full design rationale. - [`iso27001_control_mapping.md`](iso27001_control_mapping.md) — ISO 27001 mapping companion. - [`supply_chain_security.md`](supply_chain_security.md) — SBOM + pip-audit + bandit. - [`audit_event_catalog.md`](audit_event_catalog.md) — audit-event vocabulary. diff --git a/docs/usermanuals/en/reference/library-api.md b/docs/usermanuals/en/reference/library-api.md index f1e76324..d57538ea 100644 --- a/docs/usermanuals/en/reference/library-api.md +++ b/docs/usermanuals/en/reference/library-api.md @@ -47,7 +47,7 @@ assert "torch" not in sys.modules # pinned by tests/test_library_api.py - The deep guide, with three end-to-end pipeline patterns and common pitfalls, is at: [`docs/guides/library_api.md`](../../../guides/library_api.md) - The Phase 18 design rationale (stability tiers, type contract, deprecation cadence) is at: - [`docs/analysis/code_reviews/library-api-design-202605021414.md`](../../../analysis/code_reviews/library-api-design-202605021414.md) + [`docs/design/library_api.md`](../../../design/library_api.md) ## See also diff --git a/docs/usermanuals/tr/reference/library-api.md b/docs/usermanuals/tr/reference/library-api.md index d21df2ec..fefd0125 100644 --- a/docs/usermanuals/tr/reference/library-api.md +++ b/docs/usermanuals/tr/reference/library-api.md @@ -47,7 +47,7 @@ assert "torch" not in sys.modules # tests/test_library_api.py ile pinlenir - Uçtan uca üç pipeline deseni ve yaygın tuzaklar dahil derin rehber: [`docs/guides/library_api-tr.md`](../../../guides/library_api-tr.md) - Faz 18 tasarım gerekçesi (kararlılık katmanları, tip kontratı, deprecation cadence): - [`docs/analysis/code_reviews/library-api-design-202605021414.md`](../../../analysis/code_reviews/library-api-design-202605021414.md) + [`docs/design/library_api.md`](../../../design/library_api.md) ## Ayrıca bakınız diff --git a/forgelm/__init__.py b/forgelm/__init__.py index 2eb1f7d8..6bdba422 100644 --- a/forgelm/__init__.py +++ b/forgelm/__init__.py @@ -4,8 +4,7 @@ ``forgelm`` console script (and ``python -m forgelm.cli``); the Python **library API** that integrators reach via ``from forgelm import ...`` is documented in -``docs/analysis/code_reviews/library-api-design-202605021414.md`` and -finalised here in Phase 19. +``docs/design/library_api.md`` and finalised here in Phase 19. Lazy-import discipline (Phase 19): diff --git a/forgelm/_version.py b/forgelm/_version.py index af415daf..3e275acd 100644 --- a/forgelm/_version.py +++ b/forgelm/_version.py @@ -12,8 +12,7 @@ / signature changes are major). Consumers that depend on the library API can pin against this without coupling to the CLI version. -Per ``docs/analysis/code_reviews/library-api-design-202605021414.md`` -§4.3, the two versions track separately because the CLI surface and the +Per ``docs/design/library_api.md`` §4.3, the two versions track separately because the CLI surface and the Python API have different stability windows — the CLI may grow new subcommands without affecting library consumers, and library additions do not necessarily ship a new CLI feature. diff --git a/forgelm/cli/subcommands/_purge.py b/forgelm/cli/subcommands/_purge.py index 89d29585..1da6957c 100644 --- a/forgelm/cli/subcommands/_purge.py +++ b/forgelm/cli/subcommands/_purge.py @@ -1,7 +1,7 @@ """``forgelm purge`` subcommand (Phase 21 — GDPR Article 17 erasure). Implements the operator-facing surface specified in -``docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md``: +``docs/design/gdpr_erasure.md``: - ``forgelm purge --row-id --corpus `` — atomic JSONL row erasure with hashed audit event (Article 17 right-to-erasure for @@ -957,16 +957,20 @@ def _run_purge_check_policy(args, output_format: str) -> None: "no retention block" notice (which the operator would mistake for "no violations"). """ + import yaml as _yaml + from forgelm.config import ConfigError try: config_loaded = _maybe_load_config(getattr(args, "config", None), strict=True) - except (ConfigError, OSError) as exc: + except (ConfigError, OSError, _yaml.YAMLError) as exc: # OSError covers FileNotFoundError + permission denied; ConfigError - # wraps every Pydantic ValidationError raised by ``load_config``. - # Any other class would be a contract violation in the loader and - # should propagate so we fix the root cause rather than mask it - # behind a generic message. + # wraps Pydantic ValidationError raised by ``load_config``; + # ``yaml.YAMLError`` mirrors the inner catch tuple in + # ``_maybe_load_config`` so a malformed YAML that bypasses the + # ConfigError wrap still surfaces a formatted operator-facing + # message instead of a stack trace. Any other class would be a + # contract violation and should propagate. _output_error_and_exit( output_format, f"--check-policy could not load --config: {exc}", diff --git a/site/compliance.html b/site/compliance.html index 3e8775da..92a60647 100644 --- a/site/compliance.html +++ b/site/compliance.html @@ -14,28 +14,77 @@ + + + + + - + - - + + + + + + + + +
+ diff --git a/site/contact.html b/site/contact.html index 3f7db9cd..31f2703b 100644 --- a/site/contact.html +++ b/site/contact.html @@ -14,28 +14,77 @@ + + + + + - + - - + + + + + + + + +
+ diff --git a/site/css/style.css b/site/css/style.css index 9e0bd2ef..cf3e7da2 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -2,9 +2,13 @@ ForgeLM — Site Stylesheet Forge metaphor: ember orange + steel cyan Dark-first, vanilla CSS, no framework, no build step. - ============================================================ */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap'); + Note on fonts: Google Fonts CSS is loaded via a tag in + each page's (right after the preconnects). Loading via + HTML link is faster than CSS @import — the browser can fetch + the font CSS in parallel with style.css instead of waiting for + style.css to parse before discovering the @import. + ============================================================ */ /* ── Tokens ──────────────────────────────────────────────── */ :root { @@ -73,7 +77,27 @@ --glow-ember: 0 0 0 1px rgba(249,115,22,.35), 0 8px 32px rgba(249,115,22,.18); --glow-steel: 0 0 0 1px rgba(14,165,233,.35), 0 8px 32px rgba(14,165,233,.18); --transition: .22s cubic-bezier(.4,0,.2,1); + --transition-fast: .15s cubic-bezier(.4,0,.2,1); --transition-slow: .4s cubic-bezier(.4,0,.2,1); + + /* Responsive breakpoints — canonical 4-tier scale. + Existing media queries in this file use a wider mix of breakpoint + values (480 / 560 / 600 / 720 / 880 / 900 / 980 / 1100 / 1180 / + 1280) chosen for individual component layouts. We do NOT migrate + them en masse — that would risk subtle visual regressions. New + rules should reach for these tokens in CSS calc() / clamp() + contexts; @media queries still need literal values per CSS spec. */ + --bp-sm: 480px; /* small mobile */ + --bp-md: 768px; /* tablet portrait */ + --bp-lg: 900px; /* small desktop / wizard mobile cutoff */ + --bp-xl: 1200px; /* wide desktop */ + + /* Terminal/window-control dot colors — macOS-style red/yellow/green + used in the hero terminal mock + wizard YAML preview. Tokens only + so future surfaces can stay visually consistent. */ + --dot-red: #ff5f56; + --dot-yellow: #ffbd2e; + --dot-green: #27c93f; } /* Light mode override */ @@ -103,6 +127,51 @@ --pill-danger-bg: #fee2e2; } +/* ── Wizard storage-unavailable banner ─────────────────────── + Painted only when localStorage save fails (private-mode, quota, + blocked storage). Uses role="status" + aria-live="polite" so a + screen reader announces it without trapping focus. */ +.wizard-storage-warn { + position: absolute; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 110; + max-width: min(560px, calc(100% - 2rem)); + padding: 0.7rem 1rem; + background: var(--pill-warn-bg, #fef9c3); + color: var(--pill-warn-fg, #713f12); + border-radius: var(--radius-sm); + border-left: 3px solid var(--warn, #d97706); + font-size: 0.85rem; + box-shadow: var(--shadow); +} + +/* ── Skip to main content (a11y, WCAG 2.4.1 Bypass Blocks) ─── */ +/* Visually hidden until focused. First Tab on any page reveals + the link as a banner above the navbar so keyboard users can + jump past nav into the page's
. */ +.skip-link { + position: absolute; + top: 0.5rem; + left: 0.5rem; + z-index: 200; + padding: 0.6rem 1rem; + background: var(--ember); + color: #fff; + font-weight: 600; + border-radius: var(--radius-sm); + text-decoration: none; + transform: translateY(-200%); + transition: transform var(--transition); +} +.skip-link:focus, +.skip-link:focus-visible { + transform: translateY(0); + outline: 2px solid #fff; + outline-offset: 2px; +} + /* ── Reset ───────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } @@ -197,6 +266,59 @@ section { padding: 5rem 0; } } .text-center { text-align: center; } +.text-center-mt { + text-align: center; + margin-top: 2rem; +} + +/* ── Section + container utilities — extracted from inline ``style=""`` + attributes that previously lived in HTML. The class names describe + the design intent (alternate-band section, full-bleed container, + compact CTA card) so future authors don't reach for inline styles. +*/ +.section-alt { + background: var(--bg-alt); +} +.container-flush { + padding-left: 0; + padding-right: 0; +} +.stack-narrow { + margin-top: 4rem; + padding: 0; +} +.cta-card-compact { + padding: 2rem; +} + +/* Quickstart wizard CTA inside the manual 5-step page — compact h2 + + lead so it sits comfortably below the steps without dwarfing them. */ +.quickstart-wizard-h2 { + font-size: 1.4rem; + margin-bottom: 0.5rem; +} +.quickstart-wizard-p { + font-size: 0.95rem; + margin-bottom: 1.2rem; +} + +/* Compliance page closing remark linking to the audit cookbook. */ +.compliance-cookbook { + margin-top: 2rem; + font-size: 0.95rem; + color: var(--text-muted); + text-align: center; +} + +/* Contact page hero strip + form intro. */ +.contact-section { padding-top: 3rem; } +.contact-form-intro { + font-size: 0.9rem; + margin-bottom: 1.4rem; +} + +/* Compliance page article-grid wrapper that needs a small upper gap. */ +.legal-content-mt { margin-top: 1rem; } .text-gradient { background: linear-gradient(135deg, var(--ember) 0%, var(--steel) 100%); -webkit-background-clip: text; @@ -289,7 +411,7 @@ section { padding: 5rem 0; } color: var(--text-secondary); font-size: 0.92rem; font-weight: 500; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); } .nav-menu a:hover, .nav-menu a.active { color: var(--text); @@ -321,7 +443,7 @@ section { padding: 5rem 0; } font: 600 0.78rem var(--font-sans); color: var(--text); cursor: pointer; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); } .lang-toggle-btn:hover { border-color: var(--ember); @@ -361,7 +483,7 @@ section { padding: 5rem 0; } font: 500 0.88rem var(--font-sans); color: var(--text-secondary); cursor: pointer; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); } .lang-toggle-menu button:hover { background: var(--bg-elev); @@ -382,7 +504,7 @@ section { padding: 5rem 0; } place-items: center; cursor: pointer; color: var(--text-secondary); - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); } .theme-toggle:hover { color: var(--ember-bright); @@ -443,7 +565,7 @@ section { padding: 5rem 0; } border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); white-space: nowrap; line-height: 1.2; } @@ -502,7 +624,7 @@ section { padding: 5rem 0; } background: radial-gradient(ellipse at 30% 40%, rgba(249,115,22,.18), transparent 55%), radial-gradient(ellipse at 70% 60%, rgba(14,165,233,.14), transparent 55%); - filter: blur(40px); + filter: blur(24px); pointer-events: none; z-index: 0; } @@ -561,9 +683,16 @@ section { padding: 5rem 0; } 50% { opacity: .35; } } -.hero h1 { +/* Hero-slide titles share the same visual treatment regardless of tag. + Slide 1 uses

(the page's primary heading); slides 2 + 3 use

+ so each page has exactly one h1 — required for SEO + WCAG heading + hierarchy. The font-size scale collapses h1 + h2 to identical values + in this slot. */ +.hero h1, +.hero h2 { margin-bottom: 1.2rem; letter-spacing: -0.03em; + font-size: clamp(2rem, 5vw, 3.25rem); } .hero-lead { @@ -594,6 +723,192 @@ section { padding: 5rem 0; } } .hero-meta svg { width: 14px; height: 14px; color: var(--success); } +/* Hero rotating slider (3 personas: engineer / beginner / compliance). + Mechanics: cross-fade transitions, auto-rotate (8s) with hover-pause, + keyboard ←/→, swipe on touch, and a reduced-motion fallback that + stacks all slides linearly. The badge + cross-cutting meta strip stay + visible across slides — only the title/lead/CTA pair rotates. + + Layout note: ``.hero-personas`` becomes the grid item that replaces + the old left-column ``
``. The right-column ``.hero-terminal`` + keeps its original placement; the grid template is unchanged. */ +.hero-personas { + position: relative; +} + +.hero-slides { + position: relative; + /* min-height ensures the layout doesn't collapse during fade + transitions when the active slide is positioned absolutely; the + value is a comfortable cap for the longest pitch (compliance + slide currently around 4 lines of body copy at desktop width). */ + min-height: 320px; +} + +@media (max-width: 980px) { + .hero-slides { min-height: 360px; } +} +@media (max-width: 600px) { + .hero-slides { min-height: 380px; } +} + +.hero-slide { + position: absolute; + inset: 0 0 auto 0; + opacity: 0; + visibility: hidden; + transform: translateX(12px); + transition: + opacity 320ms ease-out, + transform 320ms ease-out, + visibility 0s linear 320ms; + pointer-events: none; +} + +.hero-slide.is-active { + position: relative; + opacity: 1; + visibility: visible; + transform: translateX(0); + transition: + opacity 320ms ease-out, + transform 320ms ease-out, + visibility 0s linear 0s; + pointer-events: auto; +} + +/* Slider pagination: 3 dots, the active dot expands and animates a + linear progress fill that mirrors the auto-rotate timer (8s). */ +.hero-slider-controls { + display: flex; + gap: 0.6rem; + align-items: center; + margin-top: 1.4rem; + margin-bottom: 1.4rem; +} + +.hero-slider-dot { + position: relative; + width: 24px; + height: 4px; + padding: 0; + border: none; + background: var(--border-strong); + border-radius: 999px; + cursor: pointer; + overflow: hidden; + transition: background 200ms ease, width 280ms ease; +} + +.hero-slider-dot:hover { background: var(--text-muted); } +.hero-slider-dot:focus-visible { + outline: 2px solid var(--ember-bright); + outline-offset: 3px; +} + +.hero-slider-dot.is-active { + width: 56px; + background: var(--border-strong); +} + +.hero-slider-dot .dot-fill { + position: absolute; + inset: 0; + background: linear-gradient(90deg, var(--ember-bright), var(--steel-bright)); + transform-origin: left center; + transform: scaleX(0); + border-radius: inherit; +} + +.hero-slider-dot.is-active .dot-fill { + animation: hero-slider-fill 8s linear forwards; +} + +.hero-personas[data-paused="true"] .hero-slider-dot.is-active .dot-fill { + animation-play-state: paused; +} + +@keyframes hero-slider-fill { + from { transform: scaleX(0); } + to { transform: scaleX(1); } +} + +/* Prev / next chevrons. Ghost by default; reveal on hover or + keyboard focus inside the slider. Hidden on small screens — touch + users get the swipe gesture instead. */ +.hero-slider-arrow { + position: absolute; + top: 35%; + transform: translateY(-50%); + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + /* Theme-aware: previously hardcoded a dark rgba background with + ``color: var(--text)``. In light mode that resolved to dark text + on dark bg, so the chevron disappeared. Mirror the theme by + using the same elevated-surface tokens the rest of the chrome + uses. */ + background: var(--bg-elev); + border: 1px solid var(--border-strong); + border-radius: 50%; + color: var(--text); + cursor: pointer; + opacity: 0; + transition: opacity 200ms ease, background 200ms ease, border-color 200ms ease; + z-index: 2; +} +.hero-slider-arrow svg { width: 14px; height: 14px; } +.hero-slider-arrow.prev { left: -52px; } +.hero-slider-arrow.next { right: -52px; } + +.hero-personas:hover .hero-slider-arrow, +.hero-personas:focus-within .hero-slider-arrow { + opacity: 0.9; +} +.hero-slider-arrow:hover { + opacity: 1; + background: var(--bg-elev); + border-color: var(--ember-bright); +} +.hero-slider-arrow:focus-visible { + opacity: 1; + outline: 2px solid var(--ember-bright); + outline-offset: 2px; +} + +@media (max-width: 1100px) { + .hero-slider-arrow.prev { left: -16px; } + .hero-slider-arrow.next { right: -16px; } +} +@media (max-width: 720px) { + .hero-slider-arrow { display: none; } +} + +/* Reduced-motion: stack all slides, hide pagination + arrows, + neutralise the timer animation. Each slide becomes its own block + the user can read linearly. */ +@media (prefers-reduced-motion: reduce) { + .hero-slide { + position: relative !important; + inset: auto !important; + opacity: 1 !important; + visibility: visible !important; + transform: none !important; + pointer-events: auto !important; + margin-bottom: 1.6rem; + } + .hero-slide:not(:first-child) { + border-top: 1px dashed var(--border); + padding-top: 1.4rem; + } + .hero-slider-controls, + .hero-slider-arrow { display: none !important; } + .hero-slides { min-height: 0; } + .hero-slider-dot.is-active .dot-fill { animation: none; } +} + /* Hero terminal */ .hero-terminal { background: var(--code-bg); @@ -623,7 +938,11 @@ section { padding: 5rem 0; } border-bottom: 1px solid var(--code-border); font-family: var(--font-mono); font-size: 0.78rem; - color: var(--text-muted); + /* Theme-locked: this bar is a dark code surface in BOTH themes, + so the title text uses --code-comment (theme-locked grey) — not + --text-muted, which flips to a dim grey-on-white in light mode + and disappears against the dark gradient. */ + color: var(--code-comment); } .terminal-bar .dots { display: flex; @@ -635,9 +954,9 @@ section { padding: 5rem 0; } height: 11px; border-radius: 50%; } -.terminal-bar .dot.r { background: #ff5f56; } -.terminal-bar .dot.y { background: #ffbd2e; } -.terminal-bar .dot.g { background: #27c93f; } +.terminal-bar .dot.r { background: var(--dot-red); } +.terminal-bar .dot.y { background: var(--dot-yellow); } +.terminal-bar .dot.g { background: var(--dot-green); } .terminal-body { padding: 1.2rem 1.3rem; @@ -648,13 +967,19 @@ section { padding: 5rem 0; } overflow-x: auto; } .terminal-body .prompt { color: var(--ember-bright); } -.terminal-body .cmd { color: var(--text); } +/* The terminal background is theme-locked dark (--code-bg stays dark + in both themes), so token colors here MUST use theme-locked code-* + variables — never page text vars (--text / --text-secondary / + --text-muted) which flip to dark in light mode and become + invisible on the dark terminal. The ``forgelm`` command itself + was disappearing in light mode for exactly this reason. */ +.terminal-body .cmd { color: var(--code-text); } .terminal-body .flag { color: var(--steel-bright); } .terminal-body .arg { color: #6ee7b7; } .terminal-body .comment { color: var(--code-comment); font-style: italic; } .terminal-body .ok { color: var(--success); } .terminal-body .warn { color: var(--warn); } -.terminal-body .out { color: var(--text-secondary); } +.terminal-body .out { color: var(--code-text); opacity: 0.78; } .terminal-body .blink { display: inline-block; width: 8px; height: 1.05em; @@ -725,7 +1050,7 @@ section { padding: 5rem 0; } padding: 1.2rem 1rem; text-align: center; position: relative; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); cursor: default; } .pipeline-stage:hover { @@ -770,7 +1095,7 @@ section { padding: 5rem 0; } border: 1px solid var(--border); border-radius: var(--radius); padding: 1.6rem; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); position: relative; overflow: hidden; } @@ -918,7 +1243,11 @@ section { padding: 5rem 0; } background: rgba(255,255,255,.03); font-family: var(--font-mono); font-size: 0.78rem; - color: var(--text-muted); + /* Theme-locked: the parent .code-block uses --code-bg (always dark + in both themes), so the language tag must use a theme-locked grey + too — --text-muted resolves to dim grey on light theme and + disappears here. */ + color: var(--code-comment); } .code-block-header .lang { display: inline-flex; @@ -950,12 +1279,15 @@ section { padding: 5rem 0; } .copy-btn { background: none; border: 1px solid var(--code-border); - color: var(--text-muted); + /* Theme-locked: lives on the dark .code-block-header bar in both + themes. --code-comment is the in-code default grey — readable + against the dark gradient in both light and dark page themes. */ + color: var(--code-comment); padding: 0.35rem 0.7rem; border-radius: 4px; cursor: pointer; font: 500 0.72rem var(--font-mono); - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); min-height: 32px; } .copy-btn:hover { color: var(--ember-bright); border-color: var(--ember); } @@ -976,7 +1308,7 @@ section { padding: 5rem 0; } border: 1px solid var(--border); border-radius: var(--radius); padding: 1.4rem; - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); position: relative; } .article-card:hover { @@ -1093,7 +1425,7 @@ section { padding: 5rem 0; } background: radial-gradient(ellipse at 30% 50%, rgba(249,115,22,.2), transparent 60%), radial-gradient(ellipse at 70% 50%, rgba(14,165,233,.18), transparent 60%); - filter: blur(40px); + filter: blur(24px); pointer-events: none; } .cta-card > * { position: relative; z-index: 1; } @@ -1135,109 +1467,1201 @@ section { padding: 5rem 0; } font-size: 1.08rem; } -/* ── Contact ─────────────────────────────────────────────── */ -.contact-grid { - display: grid; - grid-template-columns: 1.2fr 1fr; - gap: 2rem; - max-width: 980px; - margin: 0 auto; -} -@media (max-width: 880px) { - .contact-grid { grid-template-columns: 1fr; } -} - -.contact-form-card, -.contact-channels { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 1.8rem; -} -.contact-form-card h2, -.contact-channels h2 { - font-size: 1.2rem; - margin-bottom: 1.2rem; +.page-header-aside { + margin-top: 1.4rem !important; + font-size: 0.95rem !important; + color: var(--text-muted); + display: inline-flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; } -.form-row { +/* Wizard spotlight — the prominent CTA banner that lands between the + quickstart page-header and the canonical 5-step content. Designed + to be impossible to miss without overshadowing the existing manual + reference flow underneath. Two-column layout on desktop: marketing + copy + primary CTA on the left, a small visual diagram on the right + that mimics the wizard's three-stage contract (form → YAML → run). + Mobile collapses to single column. */ +.wizard-spotlight { + position: relative; + margin: -1rem 0 3rem; + padding: 2rem 2.2rem; + background: + radial-gradient(ellipse at 0% 0%, rgba(249, 115, 22, .14), transparent 55%), + radial-gradient(ellipse at 100% 100%, rgba(14, 165, 233, .12), transparent 55%), + linear-gradient(180deg, var(--bg-elev), var(--bg-card)); + border: 1px solid var(--border-strong); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; + grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); + gap: 2.4rem; + align-items: center; + overflow: hidden; } -@media (max-width: 560px) { .form-row { grid-template-columns: 1fr; } } - -.form-group { margin-bottom: 1.1rem; } -.form-group label { - display: block; - font-size: 0.82rem; - font-weight: 600; - color: var(--text-secondary); - margin-bottom: 0.4rem; +.wizard-spotlight::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(249,115,22,.4), transparent 40%, rgba(14,165,233,.4)); + opacity: 0.12; + pointer-events: none; } -.form-group input, -.form-group textarea, -.form-group select { - width: 100%; - padding: 0.65rem 0.875rem; - font: 400 0.92rem var(--font-sans); - color: var(--text); - background: var(--bg); - border: 1px solid var(--border-strong); - border-radius: var(--radius-sm); - outline: none; - transition: all var(--transition); +.wizard-spotlight > * { + position: relative; + z-index: 1; } -.form-group input:focus, -.form-group textarea:focus, -.form-group select:focus { - border-color: var(--ember); - box-shadow: 0 0 0 3px rgba(249,115,22,.15); +.wizard-spotlight-eyebrow { + display: inline-flex; + align-items: center; + gap: 0.45rem; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--ember-bright); + margin-bottom: 0.7rem; } -.form-group textarea { resize: vertical; min-height: 130px; font-family: var(--font-sans); } - -.form-submit { width: 100%; } - -.form-status { - margin-top: 1rem; - padding: 0.7rem 0.95rem; - border-radius: var(--radius-sm); - font-size: 0.9rem; - display: none; +.wizard-spotlight-eyebrow::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--ember); + box-shadow: 0 0 10px var(--ember); + animation: pulse 2s ease-in-out infinite; } -/* Status colors are theme-split: the lighter shades (#4ade80 / #f87171) - meet WCAG AA on the dark theme but fail on the light theme's white - background (~3.5:1 and ~3.6:1). Define the dark-friendly defaults here; - the [data-theme="light"] block overrides them with darker shades. */ -.form-status.success { - display: block; - background: rgba(34,197,94,.08); - border: 1px solid rgba(34,197,94,.3); - color: #4ade80; +.wizard-spotlight-title { + font-size: 1.6rem; + font-weight: 700; + letter-spacing: -0.02em; + margin-bottom: 0.6rem; + line-height: 1.25; } -.form-status.error { - display: block; - background: rgba(239,68,68,.08); - border: 1px solid rgba(239,68,68,.3); - color: #f87171; +.wizard-spotlight-lead { + font-size: 1rem; + color: var(--text-secondary); + margin-bottom: 1.4rem; + max-width: 540px; } -[data-theme="light"] .form-status.success { color: #15803d; } /* green-700 */ -[data-theme="light"] .form-status.error { color: #b91c1c; } /* red-700 */ - -.channel-list { - list-style: none; +.wizard-spotlight-actions { display: flex; - flex-direction: column; - gap: 0.9rem; + gap: 0.8rem; + flex-wrap: wrap; } -.channel-item { +.wizard-spotlight-meta { + display: flex; + gap: 1.2rem; + flex-wrap: wrap; + margin-top: 1rem; + font-size: 0.82rem; + color: var(--text-muted); +} +.wizard-spotlight-meta span { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} +.wizard-spotlight-meta svg { + width: 14px; + height: 14px; + color: var(--success); +} + +/* Visual: three-stage diagram. Each stage is a small card with an + icon + label + tiny snippet showing what the operator gives / gets + at that step. The arrows between cards reinforce the flow. Built + in pure SVG so the icons stay crisp at any zoom. */ +.wizard-spotlight-visual { + display: flex; + flex-direction: column; + gap: 0.8rem; + align-items: stretch; +} +.wizard-spotlight-stage { + display: grid; + grid-template-columns: 36px 1fr; + gap: 0.8rem; + padding: 0.8rem 1rem; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + align-items: center; + font-size: 0.85rem; +} +.wizard-spotlight-stage-icon { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-elev); + border: 1px solid var(--border-strong); + border-radius: 8px; + color: var(--ember-bright); +} +.wizard-spotlight-stage-icon svg { + width: 18px; + height: 18px; +} +.wizard-spotlight-stage-label { + font-weight: 600; + color: var(--text); + display: block; +} +.wizard-spotlight-stage-snippet { + font-family: var(--font-mono); + font-size: 0.74rem; + color: var(--text-muted); + margin-top: 0.15rem; +} +.wizard-spotlight-arrow { + margin: -0.4rem 0; + text-align: center; + color: var(--text-muted); + height: 16px; + display: flex; + align-items: center; + justify-content: center; +} +.wizard-spotlight-arrow svg { + width: 14px; + height: 14px; +} + +/* Five-step section heading — small introducing label that sits + between the spotlight and the existing ``.steps`` block. Helps the + reader understand they have two equally-valid paths (wizard above + OR manual below). */ +.steps-intro { + margin-bottom: 1.6rem; + font-size: 0.86rem; + color: var(--text-muted); + display: flex; + align-items: center; + gap: 0.6rem; + flex-wrap: wrap; +} +.steps-intro::before { + content: ''; + display: block; + flex: 1; + height: 1px; + background: var(--border); + min-width: 30px; +} +.steps-intro::after { + content: ''; + display: block; + flex: 1; + height: 1px; + background: var(--border); + min-width: 30px; +} + +@media (max-width: 900px) { + .wizard-spotlight { + grid-template-columns: 1fr; + gap: 1.6rem; + padding: 1.6rem 1.4rem; + } + .wizard-spotlight-title { + font-size: 1.35rem; + } +} + +/* Inline link-styled CTA used by the wizard trigger and similar + "secondary action" affordances inside copy-heavy paragraphs. + Renders like a link but keeps the button semantics so screen + readers announce it as actionable. */ +.link-cta { + display: inline-flex; + align-items: center; + gap: 0.3rem; + background: none; + border: none; + padding: 0; + font: inherit; + color: var(--ember-bright); + font-weight: 600; + cursor: pointer; + text-decoration: underline; + text-decoration-color: rgba(251, 146, 60, 0.4); + text-underline-offset: 3px; + transition: color var(--transition), text-decoration-color var(--transition); +} +.link-cta:hover, +.link-cta:focus-visible { + color: var(--ember); + text-decoration-color: var(--ember); + outline: none; +} +.link-cta:focus-visible { + outline: 2px solid var(--ember-bright); + outline-offset: 4px; + border-radius: 2px; +} + +/* ── Wizard modal ────────────────────────────────────────── */ +/* Modal-as-app dialog used by the quickstart.html in-browser YAML + wizard. Opens via [data-wizard-open] triggers or via the + ?wizard=open URL parameter (used by the home-page Beginner-slide + CTA). The dialog stays in the DOM and is toggled visible — keeping + the rendered tree avoids re-mounting the YAML preview on every + open. ARIA: role=dialog + aria-modal=true on the inner container; + the outer wrapper carries [hidden] when closed so the modal is + removed from the accessibility tree entirely. */ +.wizard-modal { + position: fixed; + inset: 0; + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + animation: wizard-modal-in 220ms ease-out; +} +.wizard-modal[hidden] { display: none; } + +@keyframes wizard-modal-in { + from { opacity: 0; } + to { opacity: 1; } +} + +.wizard-backdrop { + position: absolute; + inset: 0; + background: rgba(8, 10, 16, 0.78); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + cursor: pointer; +} + +.wizard-dialog { + position: relative; + width: min(1080px, 100%); + /* Use a *definite* height (not max-height) so the inner flex/grid + children get a resolvable frame to overflow against. With only + ``max-height``, ``flex: 1`` on .wizard-body resolves its + ``flex-basis: 0%`` against an indefinite parent, the body sizes + to content, and the step pane's ``overflow-y: auto`` never fires. + This was the root cause of the "use-case / model / dataset + screens won't scroll" bug — those steps have just enough content + to overflow on smaller laptops, but the dialog was free to grow + so nothing clipped. Fixing the dialog at 720px (or 100vh − 4rem + on small screens) gives the body + pane a definite frame and + scroll fires reliably. */ + height: min(720px, calc(100vh - 4rem)); + background: var(--bg-card); + border: 1px solid var(--border-strong); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + display: flex; + flex-direction: column; + overflow: hidden; + animation: wizard-dialog-in 240ms cubic-bezier(.4, 0, .2, 1); +} + +@keyframes wizard-dialog-in { + from { opacity: 0; transform: translateY(12px) scale(.97); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +/* Header: progress + step counter + detail toggle + close */ +.wizard-header { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem 1.4rem; + border-bottom: 1px solid var(--border); + background: var(--bg-elev); +} + +.wizard-progress { + flex: 1; + display: flex; + align-items: center; + gap: 0.9rem; +} +.wizard-progress-track { + flex: 1; + height: 5px; + background: var(--border); + border-radius: 999px; + overflow: hidden; + position: relative; +} +.wizard-progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--ember-bright), var(--steel-bright)); + border-radius: inherit; + transition: width 320ms cubic-bezier(.4, 0, .2, 1); +} +.wizard-progress-counter { + font-size: 0.78rem; + color: var(--text-muted); + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.wizard-header-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.wizard-detail-toggle { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.4rem 0.8rem; + background: transparent; + border: 1px solid var(--border-strong); + border-radius: 999px; + color: var(--text-secondary); + font-size: 0.78rem; + cursor: pointer; + transition: background var(--transition), border-color var(--transition), color var(--transition); +} +.wizard-detail-toggle:hover, +.wizard-detail-toggle.is-active { + background: var(--bg); + border-color: var(--ember-bright); + color: var(--ember-bright); +} +.wizard-detail-toggle[hidden] { display: none; } + +.wizard-close { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: 1px solid transparent; + border-radius: 50%; + color: var(--text-muted); + cursor: pointer; + transition: background var(--transition), color var(--transition), border-color var(--transition); +} +.wizard-close svg { width: 18px; height: 18px; } +.wizard-close:hover { + background: var(--bg); + color: var(--text); + border-color: var(--border-strong); +} + +/* Body: 60/40 split — step pane + YAML preview. + + Three things that all have to be true for the step pane to scroll: + + 1. .wizard-dialog has a *definite* height (set above as + ``height: min(720px, calc(100vh - 4rem))``). Without that, the + flex chain has no frame to overflow against. + + 2. .wizard-body has ``min-height: 0`` so it can shrink under + ``flex: 1`` instead of pinning to its grid content's intrinsic + height. + + 3. .wizard-body declares ``grid-template-rows: minmax(0, 1fr)``. + THIS is the one that bit us repeatedly: a grid container with + no explicit row sizing creates an implicit ``auto`` row, which + grows to max-content. So the grid row was sizing to the step + pane's content, the step pane was filling its track, and the + pane's ``overflow-y: auto`` had nothing to overflow against — + it scrolled relative to its own (content-sized) height, i.e. + not at all. ``minmax(0, 1fr)`` pins the row to the body's + definite height, the step pane fills 1fr, and scroll fires + whenever content exceeds the available row height. +*/ +.wizard-body { + flex: 1; + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr); + gap: 0; + min-height: 0; + overflow: hidden; +} + +.wizard-step-pane { + /* Padding is symmetric (2rem all around) so the LAST item in a + scrolled pane has the same breathing room as the FIRST. + Previously the bottom was 1.4rem and the final hint paragraph + hugged the bottom edge — readable but visually cramped, and on + touch devices the scrollbar overlapped the last word. */ + padding: 2rem; + overflow-y: auto; + overflow-x: hidden; + /* ``min-height: 0`` plus the dialog's definite height (above) + makes ``overflow-y: auto`` fire on every step. */ + min-height: 0; + /* ``scrollbar-gutter: stable`` reserves space for the scrollbar so + the layout doesn't reflow when content grows past the fold. */ + scrollbar-gutter: stable; +} + +.wizard-yaml-pane { + background: var(--code-bg); + border-left: 1px solid var(--code-border); + display: flex; + flex-direction: column; + min-width: 0; +} +.wizard-yaml-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.8rem 1rem; + background: linear-gradient(180deg, #1a1f2c, #141823); + border-bottom: 1px solid var(--code-border); + font-family: var(--font-mono); + font-size: 0.78rem; + /* Theme-locked: dark gradient bar in BOTH themes — see .terminal-bar + above for the same constraint. --code-comment survives the light + toggle. */ + color: var(--code-comment); +} +.wizard-yaml-toggle { + display: inline-flex; + align-items: center; + gap: 0.4rem; + background: transparent; + border: none; + padding: 0; + color: inherit; + font: inherit; + cursor: pointer; +} +.wizard-yaml-toggle svg { + width: 14px; + height: 14px; + transition: transform var(--transition); +} +@media (min-width: 901px) { + /* On desktop the YAML pane is always visible; the toggle chevron + sits silently and is keyboard-only useful for collapsing the + pane when the step pane needs more horizontal room. */ + .wizard-yaml-toggle svg { transform: rotate(-90deg); } +} + +.wizard-yaml-copy { + display: inline-flex; + align-items: center; + gap: 0.3rem; + background: transparent; + border: 1px solid var(--code-border); + border-radius: var(--radius-sm); + /* Theme-locked: lives on the dark .wizard-yaml-header in both + themes. Hover stays on the dark surface so the button doesn't + flash light-on-light when the page is in light mode. */ + color: var(--code-comment); + font: inherit; + padding: 0.2rem 0.5rem; + cursor: pointer; + transition: background var(--transition), border-color var(--transition), color var(--transition); +} +.wizard-yaml-copy:hover { + background: rgba(255, 255, 255, 0.06); + border-color: var(--code-text); + color: var(--code-text); +} +.wizard-yaml-copy.is-copied { + border-color: var(--success); + color: var(--success); +} + +.wizard-yaml-output { + flex: 1; + margin: 0; + padding: 1.2rem 1.3rem; + overflow: auto; + font-family: var(--font-mono); + font-size: 0.8rem; + line-height: 1.65; + color: var(--code-text); + white-space: pre; +} +.wizard-yaml-output code { + font: inherit; + color: inherit; + background: none; +} +/* Inline syntax tinting for the YAML preview. */ +.wizard-yaml-output .yaml-key { color: var(--ember-bright); } +.wizard-yaml-output .yaml-string { color: var(--code-string); } +.wizard-yaml-output .yaml-number { color: var(--code-number); } +.wizard-yaml-output .yaml-bool { color: var(--code-fn); } +.wizard-yaml-output .yaml-comment { color: var(--code-comment); font-style: italic; } +.wizard-yaml-output .yaml-null { color: var(--code-comment); font-style: italic; } + +/* Footer: prev / step name / next */ +.wizard-footer { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem 1.4rem; + border-top: 1px solid var(--border); + background: var(--bg-elev); +} +.wizard-prev, +.wizard-next { + flex-shrink: 0; +} +.wizard-step-name { + flex: 1; + text-align: center; + font-size: 0.85rem; + color: var(--text-muted); + font-weight: 500; +} +.wizard-prev[disabled], +.wizard-next[disabled] { + opacity: 0.4; + cursor: not-allowed; + pointer-events: none; +} + +/* Step content building blocks. wizard.js renders these via DOM + construction; the classes here describe their visual shape. */ +.wizard-step-title { + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.02em; + margin-bottom: 0.5rem; +} +.wizard-step-desc { + color: var(--text-secondary); + margin-bottom: 1.2rem; + font-size: 0.96rem; + line-height: 1.55; +} +.wizard-step-tutorial { + margin: 0 0 1.5rem; + padding: 0.9rem 1rem; + background: var(--pill-note-bg); + border-radius: var(--radius-sm); + color: var(--pill-note-fg); + font-size: 0.88rem; + line-height: 1.55; + border-left: 3px solid var(--steel-bright); +} +.wizard-step-tutorial[hidden] { display: none; } + +.wizard-step-tutorial::before { + content: ''; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 0.5rem; + vertical-align: -2px; + background: currentColor; + -webkit-mask: url("data:image/svg+xml;utf8,") center/contain no-repeat; + mask: url("data:image/svg+xml;utf8,") center/contain no-repeat; + opacity: 0.85; +} + +/* Card grid used by use-case + base-model selection steps. */ +.wizard-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 0.8rem; + margin-bottom: 1rem; +} +.wizard-card { + position: relative; + display: flex; + flex-direction: column; + gap: 0.4rem; + padding: 1rem; + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + cursor: pointer; + text-align: left; + transition: border-color var(--transition), background var(--transition), transform var(--transition); +} +.wizard-card:hover { + border-color: var(--border-strong); + background: var(--bg-elev); +} +.wizard-card.is-selected { + border-color: var(--ember-bright); + background: rgba(249, 115, 22, 0.06); + box-shadow: 0 0 0 1px var(--ember-bright); +} +.wizard-card-title { + font-weight: 600; + font-size: 0.95rem; + color: var(--text); + display: flex; + align-items: center; + gap: 0.4rem; +} +.wizard-card-title .badge { + font-size: 0.7rem; + font-weight: 500; + padding: 0.1rem 0.5rem; + background: var(--bg-elev); + border: 1px solid var(--border-strong); + border-radius: 999px; + color: var(--text-muted); +} +.wizard-card-desc { + font-size: 0.82rem; + color: var(--text-secondary); + line-height: 1.4; +} + +/* Form rows for parameter controls (Steps 4, 5, 6). */ +.wizard-row { + display: grid; + grid-template-columns: 1fr; + gap: 0.4rem; + margin-bottom: 1rem; +} +.wizard-row-label { + display: flex; + align-items: baseline; + gap: 0.5rem; + font-size: 0.88rem; + font-weight: 500; + color: var(--text); +} +.wizard-row-label small { + font-weight: 400; + font-size: 0.78rem; + color: var(--text-muted); +} +.wizard-row-hint { + font-size: 0.78rem; + color: var(--text-muted); +} +.wizard-row-hint[hidden] { display: none; } + +/* Per-field beginner-mode detail paragraph. Sits beneath the hint and + gives 2-3 sentences of plain-language explanation about WHAT the + field means (not just what range to pick). Visually distinct from + the row hint so the operator can pattern-match: hint = quick value + guidance, detail = conceptual primer. */ +.wizard-row-detail { + margin-top: 0.5rem; + padding: 0.6rem 0.8rem; + background: var(--bg); + border-left: 2px solid var(--steel-bright); + border-radius: 4px; + font-size: 0.8rem; + line-height: 1.5; + color: var(--text-secondary); +} + +.wizard-input, +.wizard-select { + display: block; + width: 100%; + padding: 0.55rem 0.8rem; + background: var(--bg); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + color: var(--text); + font: inherit; + font-size: 0.92rem; + transition: border-color var(--transition); +} +.wizard-input:focus, +.wizard-select:focus { + outline: none; + border-color: var(--ember-bright); + box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.15); +} +.wizard-input.is-invalid { + border-color: var(--danger); +} + +/* Slider row (LoRA r / alpha, etc.). The numeric output sits to the + right of the slider so the user can see the value without polling + the preview pane. */ +.wizard-slider-row { + display: grid; + grid-template-columns: 1fr 56px; + gap: 0.7rem; + align-items: center; +} +.wizard-slider { + width: 100%; + -webkit-appearance: none; + appearance: none; + height: 4px; + background: var(--border-strong); + border-radius: 999px; + outline: none; +} +.wizard-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 18px; + height: 18px; + background: var(--ember-bright); + border-radius: 50%; + cursor: pointer; + transition: transform var(--transition); +} +.wizard-slider::-webkit-slider-thumb:hover { + transform: scale(1.15); +} +.wizard-slider::-moz-range-thumb { + width: 18px; + height: 18px; + background: var(--ember-bright); + border-radius: 50%; + cursor: pointer; + border: none; +} +.wizard-slider-value { + font-family: var(--font-mono); + font-size: 0.88rem; + color: var(--ember-bright); + text-align: right; + font-variant-numeric: tabular-nums; +} + +.wizard-toggle { + display: inline-flex; + align-items: center; + gap: 0.55rem; + cursor: pointer; + user-select: none; +} +.wizard-toggle input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + width: 36px; + height: 20px; + background: var(--border-strong); + border-radius: 999px; + cursor: pointer; + position: relative; + transition: background var(--transition); +} +.wizard-toggle input[type="checkbox"]::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + background: var(--text); + border-radius: 50%; + transition: transform var(--transition); +} +.wizard-toggle input[type="checkbox"]:checked { + background: var(--ember-bright); +} +.wizard-toggle input[type="checkbox"]:checked::after { + transform: translateX(16px); +} +.wizard-toggle input[type="checkbox"]:disabled { + opacity: 0.45; + cursor: not-allowed; +} +.wizard-toggle-label { + font-size: 0.92rem; + color: var(--text); +} + +/* High-risk callout banner shown on Step 6 when EU AI Act risk = + high-risk. Pinks the page so the user can't miss the obligation + shift. */ +.wizard-callout { + margin-top: 1rem; + padding: 0.9rem 1rem; + border-radius: var(--radius-sm); + font-size: 0.86rem; + line-height: 1.5; + background: var(--pill-danger-bg); + color: var(--pill-danger-fg); + border-left: 3px solid var(--danger); +} +.wizard-callout strong { color: inherit; } + +/* Generated YAML actions on Step 7. */ +.wizard-actions { + display: flex; + gap: 0.7rem; + flex-wrap: wrap; + margin-top: 1.5rem; +} +.wizard-run-snippet { + margin-top: 1.4rem; + background: var(--code-bg); + border: 1px solid var(--code-border); + border-radius: var(--radius-sm); + padding: 0.8rem 1rem; + font-family: var(--font-mono); + font-size: 0.82rem; + color: var(--code-text); + overflow-x: auto; +} +.wizard-run-snippet .prompt { color: var(--ember-bright); } +.wizard-run-snippet .flag { color: var(--steel-bright); } +.wizard-run-snippet .arg { color: var(--code-string); } + +/* Resume banner — surfaces when wizard.js detects a previously-saved + session in localStorage and the user reopens the modal. */ +.wizard-resume-banner { + margin-bottom: 1.4rem; + padding: 0.8rem 1rem; + background: var(--pill-tip-bg); + color: var(--pill-tip-fg); + border-left: 3px solid var(--success); + border-radius: var(--radius-sm); + font-size: 0.86rem; + line-height: 1.45; + display: flex; + align-items: center; + gap: 0.8rem; + flex-wrap: wrap; +} +.wizard-resume-banner button { + background: transparent; + border: 1px solid currentColor; + border-radius: 999px; + color: inherit; + padding: 0.25rem 0.7rem; + /* ``font: inherit`` would clobber the explicit font-size; pull the + family from the parent explicitly while keeping the size local. */ + font-family: inherit; + font-size: 0.78rem; + cursor: pointer; +} + +/* Collapsible accordion section inside a step pane. Used to group + advanced / optional fields (risk assessment, data governance, + webhooks, monitoring) without bloating the step counter. The header + is a button so keyboard users can Tab into it; aria-expanded reflects + the open/closed state for assistive tech. + + Layout: header is a 3-column grid (icon | titles | chevron). Title + + hint stack vertically inside the middle cell so a long hint wraps + cleanly without pushing the chevron off-axis. The body uses the + modern grid-template-rows 0fr→1fr trick so expand/collapse + animates smoothly without us manually measuring height. */ +.wizard-accordion { + /* Override the global ``section { padding: 5rem 0 }`` rule (line 193). + Without this, every accordion grew 80px of top + bottom padding, + leaving a tall empty frame around the header. The wizard's + accordions are layout primitives, not page sections — they need + to fit their content tightly. */ + padding: 0; + margin: 0.9rem 0; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-card); + overflow: hidden; + transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition); +} +.wizard-accordion:hover { + border-color: var(--border-strong); +} +.wizard-accordion.is-open { + border-color: rgba(249, 115, 22, 0.5); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(249, 115, 22, 0.08); +} + +.wizard-accordion-header { + width: 100%; + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 0.85rem; + padding: 0.85rem 1.05rem; + background: transparent; + border: none; + cursor: pointer; + font: inherit; + color: var(--text); + text-align: left; + transition: background var(--transition); +} +.wizard-accordion-header:hover { + background: var(--bg-elev); +} +.wizard-accordion-header:focus-visible { + outline: 2px solid var(--ember-bright); + outline-offset: -2px; +} + +/* Leading icon — sits in a soft tinted square. Closed = neutral grey; + open = ember-tinted to mirror the active border. */ +.wizard-accordion-icon { + width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + background: var(--bg-elev); + color: var(--text-secondary); + flex-shrink: 0; + transition: background var(--transition), color var(--transition); +} +.wizard-accordion-icon svg { + width: 16px; + height: 16px; +} +.wizard-accordion.is-open .wizard-accordion-icon { + background: rgba(249, 115, 22, 0.12); + color: var(--ember-bright); +} + +/* Title + hint stack — single column, tight gap so the two lines read + as one logical label. ``min-width: 0`` lets long hints truncate via + the wrapper rather than overflow the row. */ +.wizard-accordion-titles { + display: flex; + flex-direction: column; + gap: 0.18rem; + min-width: 0; +} +.wizard-accordion-title { + font-weight: 600; + font-size: 0.95rem; + line-height: 1.3; + color: var(--text); +} +.wizard-accordion-hint { + font-size: 0.8rem; + color: var(--text-muted); + font-weight: 400; + line-height: 1.45; +} + +.wizard-accordion-chevron { + width: 20px; + height: 20px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + transition: transform 240ms cubic-bezier(.4, 0, .2, 1), color var(--transition); +} +.wizard-accordion-chevron svg { + width: 16px; + height: 16px; +} +.wizard-accordion.is-open .wizard-accordion-chevron { + color: var(--ember-bright); + transform: rotate(180deg); +} + +/* Closed accordion = header only. ``display: none`` is the bluntest + tool but the most reliable: it removes the body-wrap from layout + entirely, so a closed accordion is exactly the height of its + header button. The two prior attempts (grid-template-rows: 0fr; + max-height: 0) both failed in real browsers — content kept + reporting min-content height despite overflow:hidden, leaving + closed accordions as tall empty frames. We forfeit the slide + animation in exchange for visual correctness. The chevron rotation + + open-state ember accent still telegraph state changes. */ +.wizard-accordion-body-wrap { + display: none; +} +.wizard-accordion.is-open .wizard-accordion-body-wrap { + display: block; +} +.wizard-accordion-body { + border-top: 1px solid var(--border); + padding: 1rem 1.05rem 0.9rem; +} + +/* Reduced-motion users get instant chevron / hover transitions. */ +@media (prefers-reduced-motion: reduce) { + .wizard-accordion, + .wizard-accordion-icon, + .wizard-accordion-chevron { + transition: none !important; + } +} +.wizard-input[rows] { /* textareas styled the same as inputs */ + font-family: var(--font-sans); + resize: vertical; + min-height: 60px; +} + +/* Validation error pinned under an invalid input. */ +.wizard-error { + margin-top: 0.4rem; + font-size: 0.78rem; + color: var(--danger); +} +.wizard-error[hidden] { display: none; } + +/* ── Wizard responsive (mobile + reduced-motion) ─────────── */ +/* On narrow viewports the dialog goes fullscreen and the YAML + preview collapses into an accordion under the step pane so the + user has full vertical room for the controls. */ +@media (max-width: 900px) { + .wizard-modal { padding: 0; } + .wizard-dialog { + width: 100%; + height: 100vh; + max-height: 100vh; + border-radius: 0; + border: none; + } + .wizard-body { + grid-template-columns: 1fr; + /* ``minmax(0, 1fr)`` for the step-pane row — same reason as the + desktop rule above: without the ``min: 0`` floor a 1fr track + can refuse to shrink below its content. */ + grid-template-rows: minmax(0, 1fr) auto; + } + .wizard-step-pane { + padding: 1.4rem; + } + .wizard-yaml-pane { + border-left: none; + border-top: 1px solid var(--code-border); + max-height: 45vh; + } + .wizard-yaml-pane:not(.is-expanded) .wizard-yaml-output { + display: none; + } + .wizard-yaml-pane:not(.is-expanded) { + max-height: 44px; + } + .wizard-yaml-pane.is-expanded .wizard-yaml-toggle svg { + transform: rotate(180deg); + } + .wizard-cards { + grid-template-columns: 1fr; + } + .wizard-footer { + padding: 0.8rem 1rem; + } + .wizard-step-name { + display: none; + } +} + +/* The dialog reads as a flat block under reduced-motion: no enter + animation, no slide transitions on step changes, no progress + transition. The functionality still works, just without motion. */ +@media (prefers-reduced-motion: reduce) { + .wizard-modal, + .wizard-dialog { + animation: none !important; + } + .wizard-progress-fill { + transition: none !important; + } +} + +/* ── Contact ─────────────────────────────────────────────── */ +.contact-grid { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 2rem; + max-width: 980px; + margin: 0 auto; +} +@media (max-width: 880px) { + .contact-grid { grid-template-columns: 1fr; } +} + +.contact-form-card, +.contact-channels { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.8rem; +} +.contact-form-card h2, +.contact-channels h2 { + font-size: 1.2rem; + margin-bottom: 1.2rem; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} +@media (max-width: 560px) { .form-row { grid-template-columns: 1fr; } } + +.form-group { margin-bottom: 1.1rem; } +.form-group label { + display: block; + font-size: 0.82rem; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 0.4rem; +} +.form-group input, +.form-group textarea, +.form-group select { + width: 100%; + padding: 0.65rem 0.875rem; + font: 400 0.92rem var(--font-sans); + color: var(--text); + background: var(--bg); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + outline: none; + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); +} +.form-group input:focus, +.form-group textarea:focus, +.form-group select:focus { + border-color: var(--ember); + box-shadow: 0 0 0 3px rgba(249,115,22,.15); +} +.form-group textarea { resize: vertical; min-height: 130px; font-family: var(--font-sans); } + +.form-submit { width: 100%; } + +.form-status { + margin-top: 1rem; + padding: 0.7rem 0.95rem; + border-radius: var(--radius-sm); + font-size: 0.9rem; + display: none; +} +/* Status colors are theme-split: the lighter shades (#4ade80 / #f87171) + meet WCAG AA on the dark theme but fail on the light theme's white + background (~3.5:1 and ~3.6:1). Define the dark-friendly defaults here; + the [data-theme="light"] block overrides them with darker shades. */ +.form-status.success { + display: block; + background: rgba(34,197,94,.08); + border: 1px solid rgba(34,197,94,.3); + color: #4ade80; +} +.form-status.error { + display: block; + background: rgba(239,68,68,.08); + border: 1px solid rgba(239,68,68,.3); + color: #f87171; +} +[data-theme="light"] .form-status.success { color: #15803d; } /* green-700 */ +[data-theme="light"] .form-status.error { color: #b91c1c; } /* red-700 */ + +.channel-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.9rem; +} +.channel-item { display: flex; align-items: flex-start; gap: 0.9rem; padding: 1rem; border-radius: var(--radius-sm); border: 1px solid var(--border); - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); } .channel-item:hover { background: var(--bg-elev); @@ -1324,6 +2748,15 @@ section { padding: 5rem 0; } color: var(--text); margin-bottom: 0.8rem; } +.footer-brand-mark { + width: 28px; + height: 28px; + border-radius: 6px; +} +.footer-brand-icon { + width: 14px; + height: 14px; +} .footer-desc { font-size: 0.88rem; color: var(--text-muted); @@ -1380,6 +2813,13 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } } /* ── Print ──────────────────────────────────────────────── */ +/* ── Print stylesheet ──────────────────────────────────────── + ``!important`` is industry-standard for print resets: the cascade + has already applied dark-theme tokens, animation surfaces, and + chrome backdrops, all of which need to be force-overridden for + paper output. A scoped-reset alternative would require either + sweeping selector duplication or a body[data-printing] hook — + both worse for maintenance than the explicit !important here. */ @media print { .navbar, .nav-hamburger, .theme-toggle, .lang-toggle, .hero-terminal, .copy-btn, footer { display: none !important; } @@ -1542,7 +2982,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } border-radius: var(--radius-sm); border-left: 2px solid transparent; margin-left: -2px; - transition: all 0.15s ease; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease; line-height: 1.4; } .guide-nav-section a:hover { @@ -1869,7 +3309,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-card); - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); text-decoration: none !important; min-height: 70px; } @@ -1948,7 +3388,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } color: var(--text-muted); border: 1px solid var(--border); border-radius: var(--radius-sm); - transition: all var(--transition); + transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform var(--transition), box-shadow var(--transition), opacity var(--transition); } .guide-edit:hover { color: var(--ember-bright); @@ -2024,7 +3464,7 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } color: var(--text); text-decoration: none; border-left: 3px solid transparent; - transition: all 0.12s ease; + transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease; } .guide-search-results a:hover, .guide-search-results a.focused { @@ -2130,3 +3570,25 @@ body:not(.i18n-ready) { animation: i18n-failsafe 0.8s linear forwards; } [data-theme="light"] .guide-content thead { background: #f7f8fb; } [data-theme="light"] .guide-content tbody tr:hover { background: #fafbfd; } [data-theme="light"] .guide-content .mermaid { background: #fafbfd; } + +/* WCAG 1.4.3 (AA) contrast fixes — accent tokens that pass on dark + backgrounds (--ember-bright #fb923c, --steel-bright #38bdf8) drop + below 4.5:1 on the elevated light backgrounds (#f7f8fb / #fff / + tinted pills). Swap them for the corresponding ``-deep`` tokens in + light mode so inline code, use-case tags, article numbers and + table-first-column code keep their semantic colour but pass AA. */ +[data-theme="light"] code:not(pre code), +[data-theme="light"] .guide-content :not(pre) > code { + color: var(--ember-deep); +} +[data-theme="light"] .use-case-tag, +[data-theme="light"] .article-num, +[data-theme="light"] .guide-content td:first-child code { + color: var(--steel-deep); +} + +/* Hide the wizard modal in print output too — without this, a left- + open wizard would print over the page content. */ +@media print { + .wizard-modal { display: none !important; } +} diff --git a/site/features.html b/site/features.html index 6ff39048..fdd7668c 100644 --- a/site/features.html +++ b/site/features.html @@ -14,28 +14,77 @@ + + + + + - + - - + + + + + + + + +
+ diff --git a/site/guide.html b/site/guide.html index fe335d38..fdba7a76 100644 --- a/site/guide.html +++ b/site/guide.html @@ -14,25 +14,74 @@ + + + + + - + - - - + + + + + + + + 0 @@ -40,11 +89,11 @@
-
+
Capabilities @@ -275,28 +385,28 @@

Everything the modern post-training stack needs
-
-
+
+

Document ingestion

PDF, DOCX, EPUB, TXT, Markdown → SFT-ready JSONL.

forgelm ingest --strategy markdown
-
+
-
-
+
+

Dataset audit

PII, secrets, leakage, language detect, near-duplicates.

forgelm audit streaming
-
+

-
-
+
+

QLoRA + DoRA + GaLore

4-bit NF4 quantization with PEFT adapters.

@@ -304,30 +414,30 @@

QLoRA + DoRA + GaLore

DoRA GaLore
-
+
-
-
+
+

Safety evaluation

Llama Guard with confidence-weighted scoring.

Llama Guard S1-S14
-
+ -
-
+
+

Benchmarking

Plug-in lm-evaluation-harness tasks.

lm-eval-harness judge
-
+ -
-
+
+

Distributed training

DeepSpeed ZeRO presets, FSDP, multi-GPU configs.

@@ -335,48 +445,48 @@

Distributed training

FSDP MoE
-
+ -
-
+
+

VRAM fit-check

Pre-flight memory estimator.

--fit-check
-
+ -
-
+
+

Interactive chat

Streaming REPL with safety routing.

forgelm chat
-
+ -
-
+
+

Export & deploy

GGUF export with 6 quant levels.

GGUF deploy
-
+ -
-
+
+

Library API

Import forgelm.api and embed training, audit, safety eval, and verify-audit calls inside your own Python pipelines.

forgelm.api embed
-
+ -
-
+
+

ISO 27001 / SOC 2 alignment

Audit-trail, change-management, data-lineage, and supply-chain evidence the deployer's auditor asks for. Software is aligned, not certified.

@@ -384,23 +494,23 @@

ISO 27001 / SOC 2 alignment

pip-audit bandit
-
+ -
-
+
+

GDPR rights tooling

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

forgelm purge forgelm reverse-pii
-
+ - @@ -415,7 +525,7 @@

One YAML, end-to-end run

A SFT → DPO run with safety eval, benchmark gate, and Annex IV export.

-
+
configs/policy-bot.yaml @@ -477,7 +587,7 @@

One YAML, end-to-end run

-
+
Built for these workflows @@ -487,7 +597,7 @@

Use cases that actually ship

- +

Domain expert from policy PDFs

@@ -498,7 +608,7 @@

Domain expert from policy PDFs

- +

Customer support assistant

@@ -509,7 +619,7 @@

Customer support assistant

- +

Code copilot fine-tune

@@ -520,7 +630,7 @@

Code copilot fine-tune

- +

Reasoning RL with GRPO

@@ -547,13 +657,15 @@

Ship your next fine-tune in

+

+ diff --git a/site/quickstart.html b/site/quickstart.html index 7909450b..634e1549 100644 --- a/site/quickstart.html +++ b/site/quickstart.html @@ -14,28 +14,77 @@ + + + + + - + - - + + + + + + + + +
+ +
+
+ Skip the YAML +

Use the in-browser wizard to build your config in 3 minutes.

+

Pick your use case, base model, dataset, and EU AI Act risk tier. The wizard writes a working quickstart-generated.yaml you can run with one command — no fine-tuning expertise required.

+
+ + Or follow the 5-step guide +
+
+ + 7 guided steps + + Live YAML preview + + EU AI Act presets +
+
+ +
+ +

Or follow the manual 5-step reference below

+
@@ -224,33 +343,143 @@

Chat, export, deploy

-
-
-

Prefer a guided wizard?

-

forgelm --wizard walks through trainer choice, model, dataset, LoRA rank, evaluation gates.

+
+
+

Prefer a guided wizard?

+

Skip the YAML — the in-browser wizard walks through trainer choice, model, dataset, LoRA rank, and evaluation gates, then writes a working config you can run with one command.

+ + + + +
+ + diff --git a/site/terms.html b/site/terms.html index 51e7b3f0..5bc2141f 100644 --- a/site/terms.html +++ b/site/terms.html @@ -14,28 +14,77 @@ + + + + + - + - - + + + + + + + + +
+ diff --git a/tests/test_gdpr_erasure.py b/tests/test_gdpr_erasure.py index df9ecdc8..6b4d2b75 100644 --- a/tests/test_gdpr_erasure.py +++ b/tests/test_gdpr_erasure.py @@ -1,7 +1,7 @@ """Phase 21 — GDPR Article 17 erasure (`forgelm purge`). Mirrors the design spec at -``docs/analysis/code_reviews/gdpr-erasure-design-202605021414.md`` §7 +``docs/design/gdpr_erasure.md`` §7 which enumerates the 11 tests Phase 21 must ship. Tests run torch-free and use synthetic JSONL fixtures so every CI matrix combo exercises the full surface. diff --git a/tests/test_library_api.py b/tests/test_library_api.py index b74d2144..6daed3e6 100644 --- a/tests/test_library_api.py +++ b/tests/test_library_api.py @@ -1,7 +1,7 @@ """Phase 19 — Library API integration tests. Verifies the public Python surface that -``docs/analysis/code_reviews/library-api-design-202605021414.md`` pins: +``docs/design/library_api.md`` pins: - Stable symbol set matches ``forgelm.__all__``. - Lazy-import discipline holds — ``import forgelm`` does NOT pull @@ -124,7 +124,7 @@ def test_dir_leaks_no_internal_names_outside_all(self) -> None: def test_design_doc_experimental_symbols_are_exported(self) -> None: """F-PR29-A3-05: symbols listed Experimental in - ``docs/analysis/code_reviews/library-api-design-202605021414.md`` + ``docs/design/library_api.md`` MUST be importable from the top-level ``forgelm`` package. Otherwise consumers who follow the design doc and write ``from forgelm import compute_minhash`` get an