diff --git a/.github/scripts/validate-git-hook.sh b/.github/scripts/validate-git-hook.sh new file mode 100755 index 00000000..098921e9 --- /dev/null +++ b/.github/scripts/validate-git-hook.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Generated by flext-infra. AI Hub owns Git hook installation. +set -euo pipefail +stage="${1:?Git hook stage is required}" +cd "$(git rev-parse --show-toplevel)" +case "$stage" in + pre-commit|pre-merge-commit) + if ! git diff --quiet --ignore-submodules=none || [ -n "$(git ls-files --others --exclude-standard)" ]; then + printf '%s\n' 'Commit blocked: stage the complete candidate before validation; partial staging is preserved.' >&2 + exit 1 + fi + ;; + pre-push) + test -z "$(git status --porcelain --untracked-files=all --ignore-submodules=none)" + ;; + *) printf 'Unsupported Git hook stage: %s\n' "$stage" >&2; exit 2 ;; +esac +# --all-files avoids pre-commit hook-impl's stash of unstaged changes. +unset SKIP PRE_COMMIT_ALLOW_NO_CONFIG +pre-commit run --all-files --hook-stage "$stage" diff --git a/.gitignore b/.gitignore index 2b8979b3..34a91cd5 100644 --- a/.gitignore +++ b/.gitignore @@ -36,9 +36,7 @@ */.hooks.json.agents-governance.json .claude/*.agents-governance.json .gemini/*.agents-governance.json -.claude/settings.json .claude/settings.local.json -.gemini/settings.json # Operator-private local config overrides /config/codegen-overrides.local.yaml @@ -359,6 +357,7 @@ CLAUDE.local.md !.github/scripts/ !.github/scripts/check-beads-policy.sh !.github/scripts/check-cqrs-compliance.sh +!.github/scripts/validate-git-hook.sh !.github/scripts/flext-law-loop.sh !tests/fixtures/ !tests/fixtures/ci/ diff --git a/Makefile b/Makefile index 9f7ed1c4..7af5521d 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,8 @@ endif endif # End SECTION: REPOSITORY_ROOT isolation # === SECTION: verb dispatch (managed) === -# Source: config:make.verbs and the canonical gate vocabulary. +# Source: config:make.verbs and the canonical gate vocabulary. A verb exists +# only in the profiles it declares (make.verbs[].profiles). PUBLIC_VERBS := help setup upg build check test test-full fmt fix fix-enforcement audit status docs clean release-plan release-version release-tag release-build publication gen initialize mod waza duplication sonarcloud-sync BUILTIN_VERBS := help setup upg build check test test-full fmt fix fix-enforcement audit status docs clean release-plan release-version release-tag release-build publication gen initialize mod waza duplication sonarcloud-sync SCRIPT_VERBS := diff --git a/docs/api-reference/generated/overview.md b/docs/api-reference/generated/overview.md index 7cadda35..d30569ca 100644 --- a/docs/api-reference/generated/overview.md +++ b/docs/api-reference/generated/overview.md @@ -8,27 +8,14 @@ -- Package: `flext_web` - Version: `0.12.0` - Description: FLEXT Web - Modern Web Interface for FLEXT Platform -- Doc summary: Flext Web package. -- Classifiers: Development Status :: 3 - Alpha, Framework :: Flask, Intended Audience :: - Developers, Operating System :: OS Independent, Programming Language :: Python :: 3 :: - Only, Programming Language :: Python :: 3.13, Topic :: Internet :: WWW/HTTP :: Dynamic - Content, Topic :: Software Development :: Libraries :: Python Modules, Typing :: Typed -- Project class: `domain` -- Keywords: `enterprise`, `flask`, `flext`, `frontend`, `typed`, `web` -- Main facades: `FlextWeb`, `FlextWebApp`, `FlextWebAuth`, `FlextWebCli`, - `FlextWebConfig`, `FlextWebConstants`, `FlextWebEntities`, `FlextWebHandlers` (+8 - more) -- Alias exports: `c`, `d`, `e`, `h`, `m`, `p`, `r`, `s`, `t`, `u`, `x` -- Public symbol exports: `FlextWeb`, `FlextWebApp`, `FlextWebAuth`, `FlextWebCli`, - `FlextWebConfig`, `FlextWebConstants`, `FlextWebEntities`, `FlextWebHandlers`, - `FlextWebHealth`, `FlextWebModels` (+10 more) -- Exported module shortcuts: `services` -- Generated module pages: `15` +- Governed projects: `0` +- Project classes: _none_ + +Generated from workspace discovery, `pyproject.toml`, public exports, and docstrings. ## Next Pages -- [Public API](public-api.md) -- [Module Index](modules/index.md) +- [Workspace Module Pages](projects/index.md) +- [Project Catalog](../../projects/generated/catalog.md) diff --git a/docs/api-reference/generated/projects/index.md b/docs/api-reference/generated/projects/index.md new file mode 100644 index 00000000..f4555490 --- /dev/null +++ b/docs/api-reference/generated/projects/index.md @@ -0,0 +1,13 @@ +# Workspace Module Pages + + + +- No sections found + + + + + +Each project renders one page per public module, driven by docstrings. + +_No projects discovered._ diff --git a/docs/projects/generated/catalog.md b/docs/projects/generated/catalog.md new file mode 100644 index 00000000..4fe3eba1 --- /dev/null +++ b/docs/projects/generated/catalog.md @@ -0,0 +1,15 @@ +# FLEXT Project Catalog + + + +- No sections found + + + + + +Project links resolve to the generated root API reference for each governed FLEXT +package. + +| project | class | package | description | +|---|---|---|---| diff --git a/mkdocs.yml b/mkdocs.yml index 8764a07f..925d9c36 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,10 +1,9 @@ # AUTO-GENERATED — DO NOT EDIT MANUALLY -site_name: flext-web Documentation -site_description: Generated documentation for flext-web +site_name: flext-web +site_description: Workspace documentation generated from code and curated root docs site_url: https://github.com/flext-sh/flext/blob/main/README.md repo_name: flext-sh/flext repo_url: https://github.com/flext-sh/flext -edit_uri: edit/main/flext-web/ docs_dir: docs site_dir: .reports/docs/site @@ -88,7 +87,9 @@ markdown_extensions: custom_checkbox: true # MkDocs discovers the complete documentation tree, including maintained pages. -# A fixed three-page nav silently omitted every domain guide and decision record. +# A fixed three-page nav silently omitted guides, standards, and decisions. +not_in_nav: | + api-reference/generated/** validation: omitted_files: warn absolute_links: warn diff --git a/pyproject.toml b/pyproject.toml index ee6524df..70b8f983 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ +# @flext-generated: continuous +# @flext-owner: flext-infra/config/codegen.yaml + flext-infra/src/flext_infra/templates/project/base/pyproject.toml.j2 +# @flext-adjust: MANAGED=conflict_sections + overwrite_project_keys. CUSTOM=preserve_project_keys and [tool.*] outside conflict_sections. Never edit this projection. +# @flext-regenerate: make gen + [build-system] build-backend = "hatchling.build" requires = ["hatchling"] @@ -129,7 +134,6 @@ exclude_also = [ "if TYPE_CHECKING:", "if typing.TYPE_CHECKING:", ] -fail_under = 45 precision = 2 show_missing = true skip_covered = false diff --git a/src/flext_web/_protocols/lifecycle.py b/src/flext_web/_protocols/lifecycle.py index 26fc1878..b8f33be8 100644 --- a/src/flext_web/_protocols/lifecycle.py +++ b/src/flext_web/_protocols/lifecycle.py @@ -65,5 +65,20 @@ def stop_service() -> p.Result[bool]: """Stop the web service.""" ... + @runtime_checkable + class WebServiceRules(p.Base, Protocol): + """Protocol for a web service that validates its own business rules. + + Declares only the ``validate_business_rules`` capability this member + actually consumes, deliberately not extending ``p.Service`` while that + base still carries unimplemented members (``service_info``, ``ok``, + ``fail_op``) which would make this protocol structurally unsatisfiable + by real services. + """ + + def validate_business_rules(self) -> p.Result[bool]: + """Validate the service's own business rules.""" + ... + __all__: list[str] = ["FlextWebProtocolsLifecycle"] diff --git a/tests/unit/test_services.py b/tests/unit/test_services.py index d3944f37..f756704f 100644 --- a/tests/unit/test_services.py +++ b/tests/unit/test_services.py @@ -4,7 +4,7 @@ from flext_tests import tm -from flext_web import web +from flext_web import p, web from tests import m from tests.fixtures import TestsFlextWebAuthFixture @@ -132,3 +132,15 @@ def test_get_service_status(self) -> None: tm.ok(result) tm.that(result.value.service, eq="flext-web-api") tm.that(result.value.capabilities, has="flask_support") + + @staticmethod + def _is_service_rules(candidate: p.Base) -> bool: + """Report structural conformance without a type-narrowed argument.""" + return isinstance(candidate, p.Web.WebServiceRules) + + def test_web_satisfies_its_own_service_rules_protocol(self) -> None: + """The real composed facade structurally satisfies its own protocol.""" + tm.that(self._is_service_rules(web), eq=True) + result = web.validate_business_rules() + tm.ok(result) + tm.that(result.value, eq=True) diff --git a/uv.lock b/uv.lock index 12f30360..f0158eef 100644 --- a/uv.lock +++ b/uv.lock @@ -448,7 +448,7 @@ wheels = [ [[package]] name = "flext-cli" version = "0.12.0" -source = { git = "https://github.com/flext-sh/flext-cli.git?rev=0.12.0-dev#ce36f45d6438e0b90ca809715a1be6dc65cfae05" } +source = { git = "https://github.com/flext-sh/flext-cli.git?rev=0.12.0-dev#61590846cf959631e681b934e4b37f46347530c1" } dependencies = [ { name = "annotated-types" }, { name = "beartype" }, @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "flext-core" version = "0.12.0" -source = { git = "https://github.com/flext-sh/flext-core.git?rev=0.12.0-dev#d65ba487fd56312f1a5fc809ea478f0413634978" } +source = { git = "https://github.com/flext-sh/flext-core.git?rev=0.12.0-dev#150f3c757c009515a324febe0cda229a7a52dd03" } dependencies = [ { name = "annotated-types" }, { name = "beartype" }, @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "flext-infra" version = "0.12.0" -source = { git = "https://github.com/flext-sh/flext-infra.git?rev=0.12.0-dev#4563c04ac340487ffab47cc55e741ef6c09d7198" } +source = { git = "https://github.com/flext-sh/flext-infra.git?rev=0.12.0-dev#a3eba48958c6141d4bc03f97857fd5963f966bbd" } dependencies = [ { name = "annotated-types" }, { name = "beartype" }, @@ -518,7 +518,7 @@ dependencies = [ [[package]] name = "flext-tests" version = "0.12.0" -source = { git = "https://github.com/flext-sh/flext-tests.git?rev=0.12.0-dev#7dd55ef8e8f1b7a1652e16247a1842dd22471b4e" } +source = { git = "https://github.com/flext-sh/flext-tests.git?rev=0.12.0-dev#58ba7e1d88887cbf3c8dc165a437c964e4e23677" } dependencies = [ { name = "annotated-types" }, { name = "beartype" }, @@ -1273,11 +1273,11 @@ wheels = [ [[package]] name = "nodeenv" -version = "1.10.0" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/105de02c1322cfada6d9710d9146ef8026419d433c9d08359a2d35805811/nodeenv-1.11.0.tar.gz", hash = "sha256:3ce8fe5b71d16e8af7039ca65257354100bc772965d6bc549070649e53b1b146", size = 99301, upload-time = "2026-09-26T11:29:21.367Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/54/c8/12811c9b48fde162bb72b6f2e78fada9a247a09d7bb5be2050a5d099c77b/nodeenv-1.11.0-py2.py3-none-any.whl", hash = "sha256:edaa16e6c14d7cf395d75d4bbd5a26390f4dc06501a33b4e76282b02cc688a25", size = 34277, upload-time = "2026-09-26T11:29:19.933Z" }, ] [[package]] @@ -1338,11 +1338,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.15" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/e0/7c20b5d0e0f147c40a934e8f9e9f717bd6e3e372c4d58ca5c2fcce2b1652/platformdirs-4.11.15.tar.gz", hash = "sha256:d7419e973b2b740d428200130f80c0e79304d1c71081001db911e15b26a3a6d4", size = 46059, upload-time = "2026-09-26T02:04:58.142Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/4d/e78afe1b449720c481884ca0a2f960f85f9ffdaa34b2d127b5427422c564/platformdirs-4.12.0.tar.gz", hash = "sha256:095be5c143382b1bee917c4f3e9987a0d8d6a582261f1d061ad0c403b7695b5b", size = 58964, upload-time = "2026-09-26T15:26:00.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/83/246d3052d043683cc5f400ab5748bd07a99fbdf311dc771cfad7e3f9e7e0/platformdirs-4.11.15-py3-none-any.whl", hash = "sha256:84508421c77d3c6462e71dd8c0d2776c2ace0e94eb006dc4fd383bc1dcf5a176", size = 26689, upload-time = "2026-09-26T02:04:56.71Z" }, + { url = "https://files.pythonhosted.org/packages/60/3a/723ad221b7fef91ccd947f9fddbe84d38fa573e2723317aaa67bfde41015/platformdirs-4.12.0-py3-none-any.whl", hash = "sha256:f6fb2960f2f2eb0870f820f7e49e49e6c0f0589c637f34d33b260b07591327c9", size = 32965, upload-time = "2026-09-26T15:25:59.496Z" }, ] [[package]] @@ -1809,8 +1809,8 @@ wheels = [ [[package]] name = "rope" -version = "1.14.0+dc.2" -source = { git = "https://github.com/marlon-costa-dc/rope.git?rev=1.14.0%2Bdc.2#d2571e8cfabe2e00de7783e153403ac0cf4828d4" } +version = "1.14.0+dc.3" +source = { git = "https://github.com/marlon-costa-dc/rope.git?rev=1.14.0%2Bdc.3#64134bf54db1c02ca19602f320d26e1a570e7786" } dependencies = [ { name = "pytoolconfig", extra = ["global"] }, ] @@ -1870,6 +1870,7 @@ wheels = [ name = "rumdl" version = "0.2.77" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/9e/dae5c1b451cddc6d82a9c0c5a84cc7d0a1c5087ae15dd916db89ed4f9bb2/rumdl-0.2.77.tar.gz", hash = "sha256:81a35938965a2153149c1351db13007f146149d49504e3ef81f5f1eb5656d412", size = 4878672, upload-time = "2026-09-26T14:54:23.375Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c0/15494f04e84fb67a76b5889c9fefe46b96e79aac9757a9f55d2af0c31bc2/rumdl-0.2.77-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:46a86c67dbfc8d4ec7643c0696e69bd0672d4105584b55e436e43982f6e74ab5", size = 6911721, upload-time = "2026-09-23T13:25:47.365Z" }, { url = "https://files.pythonhosted.org/packages/9e/d2/23708cdeadd1b352e988a277d050478eba9504a96b55149e6531990004ea/rumdl-0.2.77-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cef5890e4995e968bf87f1d6b3ec129092204987242fe68c55781c9c90829552", size = 6449218, upload-time = "2026-09-23T13:25:41.335Z" },