aviato 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aviato-0.3.0/LICENSE +21 -0
- aviato-0.3.0/PKG-INFO +21 -0
- aviato-0.3.0/README.md +331 -0
- aviato-0.3.0/aviato/__init__.py +33 -0
- aviato-0.3.0/aviato/audit.py +114 -0
- aviato-0.3.0/aviato/cli.py +2554 -0
- aviato-0.3.0/aviato/command.py +55 -0
- aviato-0.3.0/aviato/core/__init__.py +6 -0
- aviato-0.3.0/aviato/core/bootstrap.py +33 -0
- aviato-0.3.0/aviato/core/composition.py +489 -0
- aviato-0.3.0/aviato/core/consent.py +49 -0
- aviato-0.3.0/aviato/core/declaration.py +153 -0
- aviato-0.3.0/aviato/core/diagnosis.py +234 -0
- aviato-0.3.0/aviato/core/errors.py +33 -0
- aviato-0.3.0/aviato/core/file_drift_flow.py +65 -0
- aviato-0.3.0/aviato/core/filedrift.py +28 -0
- aviato-0.3.0/aviato/core/fleet.py +126 -0
- aviato-0.3.0/aviato/core/listmerge.py +67 -0
- aviato-0.3.0/aviato/core/mapmerge.py +21 -0
- aviato-0.3.0/aviato/core/marker.py +100 -0
- aviato-0.3.0/aviato/core/model.py +127 -0
- aviato-0.3.0/aviato/core/offboarding.py +138 -0
- aviato-0.3.0/aviato/core/onboarding.py +266 -0
- aviato-0.3.0/aviato/core/pathguard.py +45 -0
- aviato-0.3.0/aviato/core/ports.py +67 -0
- aviato-0.3.0/aviato/core/provision.py +94 -0
- aviato-0.3.0/aviato/core/reconcile.py +150 -0
- aviato-0.3.0/aviato/core/reconcile_flow.py +153 -0
- aviato-0.3.0/aviato/core/registry.py +212 -0
- aviato-0.3.0/aviato/core/render.py +29 -0
- aviato-0.3.0/aviato/core/repin.py +129 -0
- aviato-0.3.0/aviato/core/scaffold.py +405 -0
- aviato-0.3.0/aviato/core/selfcheck.py +156 -0
- aviato-0.3.0/aviato/core/settings_drift_flow.py +166 -0
- aviato-0.3.0/aviato/core/settingsdrift.py +142 -0
- aviato-0.3.0/aviato/core/variables.py +140 -0
- aviato-0.3.0/aviato/core/version.py +103 -0
- aviato-0.3.0/aviato/core/versioning.py +117 -0
- aviato-0.3.0/aviato/github.py +511 -0
- aviato-0.3.0/aviato/github_platform.py +1086 -0
- aviato-0.3.0/aviato/library/aviato-library.yaml +27 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/aviato-library-sc.yaml +12 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/common.yaml +14 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/node-sc.yaml +10 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/node-service-sc.yaml +12 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/python-component-sc.yaml +12 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/python-library-sc.yaml +12 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/python-sc.yaml +7 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/python-service-sc.yaml +24 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/swift-app-sc.yaml +12 -0
- aviato-0.3.0/aviato/library/bundles/scaffold/swift-sc.yaml +6 -0
- aviato-0.3.0/aviato/library/bundles/settings/baseline.yaml +43 -0
- aviato-0.3.0/aviato/library/bundles/workflows/base.yaml +9 -0
- aviato-0.3.0/aviato/library/bundles/workflows/node-service-wf.yaml +7 -0
- aviato-0.3.0/aviato/library/bundles/workflows/python-component-wf.yaml +8 -0
- aviato-0.3.0/aviato/library/bundles/workflows/python-library-wf.yaml +7 -0
- aviato-0.3.0/aviato/library/bundles/workflows/python-service-wf.yaml +7 -0
- aviato-0.3.0/aviato/library/bundles/workflows/swift-app-wf.yaml +7 -0
- aviato-0.3.0/aviato/library/docs-toolchain.yaml +3 -0
- aviato-0.3.0/aviato/library/node-service.yaml +38 -0
- aviato-0.3.0/aviato/library/pipelines.yaml +85 -0
- aviato-0.3.0/aviato/library/policy.yml +25 -0
- aviato-0.3.0/aviato/library/python-component.yaml +36 -0
- aviato-0.3.0/aviato/library/python-library.yaml +35 -0
- aviato-0.3.0/aviato/library/python-service.yaml +44 -0
- aviato-0.3.0/aviato/library/rulesets/protect-default-branch.json +60 -0
- aviato-0.3.0/aviato/library/rulesets/release-tag-format.json +41 -0
- aviato-0.3.0/aviato/library/rulesets.yml +13 -0
- aviato-0.3.0/aviato/library/scaffold/codeowners.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/contributing.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/docs-intro.yaml +6 -0
- aviato-0.3.0/aviato/library/scaffold/docs-requirements.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/editorconfig.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/files/VERSION.txt +1 -0
- aviato-0.3.0/aviato/library/scaffold/files/codeowners.txt +3 -0
- aviato-0.3.0/aviato/library/scaffold/files/contributing.md.txt +16 -0
- aviato-0.3.0/aviato/library/scaffold/files/docs-intro.md.txt +7 -0
- aviato-0.3.0/aviato/library/scaffold/files/docs-requirements.txt.txt +3 -0
- aviato-0.3.0/aviato/library/scaffold/files/editorconfig.txt +12 -0
- aviato-0.3.0/aviato/library/scaffold/files/eslint.config.mjs.txt +10 -0
- aviato-0.3.0/aviato/library/scaffold/files/gitignore-common.txt +24 -0
- aviato-0.3.0/aviato/library/scaffold/files/issue-template-bug.md.txt +17 -0
- aviato-0.3.0/aviato/library/scaffold/files/issue-template-feature.md.txt +17 -0
- aviato-0.3.0/aviato/library/scaffold/files/license.txt +21 -0
- aviato-0.3.0/aviato/library/scaffold/files/mypy-service.ini.txt +7 -0
- aviato-0.3.0/aviato/library/scaffold/files/mypy.ini.txt +5 -0
- aviato-0.3.0/aviato/library/scaffold/files/npmrc.txt +3 -0
- aviato-0.3.0/aviato/library/scaffold/files/package.json.js.txt +23 -0
- aviato-0.3.0/aviato/library/scaffold/files/package.json.ts.txt +25 -0
- aviato-0.3.0/aviato/library/scaffold/files/pr-template.md.txt +12 -0
- aviato-0.3.0/aviato/library/scaffold/files/prettierrc.json.txt +5 -0
- aviato-0.3.0/aviato/library/scaffold/files/pyproject.toml.txt +37 -0
- aviato-0.3.0/aviato/library/scaffold/files/requirements-dev.txt.txt +9 -0
- aviato-0.3.0/aviato/library/scaffold/files/ruff.toml.txt +5 -0
- aviato-0.3.0/aviato/library/scaffold/files/swift-format.txt +6 -0
- aviato-0.3.0/aviato/library/scaffold/files/swiftlint.yml.txt +7 -0
- aviato-0.3.0/aviato/library/scaffold/files/tsconfig.json.txt +11 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-docs-node-service.yml +118 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-docs-python-component.yml +118 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-docs-python-library.yml +118 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-docs-python-service.yml +114 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-docs-swift-app.yml +112 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-drift.yml +28 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-node-service.yml +120 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-python-component.yml +107 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-python-library.yml +313 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-python-service.yml +129 -0
- aviato-0.3.0/aviato/library/scaffold/files/wf-swift-app.yml +134 -0
- aviato-0.3.0/aviato/library/scaffold/files/zensical.toml.txt +10 -0
- aviato-0.3.0/aviato/library/scaffold/gitignore-common.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/issue-template-bug.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/issue-template-feature.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/license.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/node-format-config.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/node-lint-config.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/node-manifest-js.yaml +8 -0
- aviato-0.3.0/aviato/library/scaffold/node-manifest.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/node-tsconfig.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/npmrc.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/pr-template.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/python-dev-deps.yaml +6 -0
- aviato-0.3.0/aviato/library/scaffold/python-lint-config.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/python-manifest.yaml +6 -0
- aviato-0.3.0/aviato/library/scaffold/python-typecheck-config-service.yaml +8 -0
- aviato-0.3.0/aviato/library/scaffold/python-typecheck-config.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/python-version-file.yaml +6 -0
- aviato-0.3.0/aviato/library/scaffold/swift-format-config.yaml +4 -0
- aviato-0.3.0/aviato/library/scaffold/swift-lint-config.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/wf-ci-node-service.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/wf-ci-python-component.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/wf-ci-python-library.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/wf-ci-python-service.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/wf-ci-swift-app.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/wf-docs-node-service.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/wf-docs-python-component.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/wf-docs-python-library.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/wf-docs-python-service.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/wf-docs-swift-app.yaml +7 -0
- aviato-0.3.0/aviato/library/scaffold/wf-drift.yaml +5 -0
- aviato-0.3.0/aviato/library/scaffold/zensical-config.yaml +7 -0
- aviato-0.3.0/aviato/library/swift-app.yaml +49 -0
- aviato-0.3.0/aviato/library/zizmor.yml +25 -0
- aviato-0.3.0/aviato/library_source.py +119 -0
- aviato-0.3.0/aviato/paths.py +26 -0
- aviato-0.3.0/aviato/plugins/__init__.py +6 -0
- aviato-0.3.0/aviato/plugins/actionpins.py +979 -0
- aviato-0.3.0/aviato/plugins/comment_syntax.py +36 -0
- aviato-0.3.0/aviato/plugins/denylist.txt +41 -0
- aviato-0.3.0/aviato/plugins/version_formats.py +223 -0
- aviato-0.3.0/aviato/plugins/zizmor_scan.py +116 -0
- aviato-0.3.0/aviato/policy.py +64 -0
- aviato-0.3.0/aviato/repos.py +120 -0
- aviato-0.3.0/aviato/rulesets.py +229 -0
- aviato-0.3.0/aviato/validation.py +1026 -0
- aviato-0.3.0/aviato.egg-info/PKG-INFO +21 -0
- aviato-0.3.0/aviato.egg-info/SOURCES.txt +192 -0
- aviato-0.3.0/aviato.egg-info/dependency_links.txt +1 -0
- aviato-0.3.0/aviato.egg-info/entry_points.txt +2 -0
- aviato-0.3.0/aviato.egg-info/requires.txt +14 -0
- aviato-0.3.0/aviato.egg-info/top_level.txt +1 -0
- aviato-0.3.0/pyproject.toml +70 -0
- aviato-0.3.0/setup.cfg +4 -0
- aviato-0.3.0/tests/test_audit.py +104 -0
- aviato-0.3.0/tests/test_cli_apply_rulesets.py +271 -0
- aviato-0.3.0/tests/test_cli_doctor.py +232 -0
- aviato-0.3.0/tests/test_cli_drift_report.py +318 -0
- aviato-0.3.0/tests/test_cli_is_highest.py +31 -0
- aviato-0.3.0/tests/test_cli_lint_actions.py +33 -0
- aviato-0.3.0/tests/test_cli_onboard.py +294 -0
- aviato-0.3.0/tests/test_cli_onboard_proposal.py +313 -0
- aviato-0.3.0/tests/test_cli_onboard_write.py +498 -0
- aviato-0.3.0/tests/test_cli_provision.py +291 -0
- aviato-0.3.0/tests/test_cli_reconcile.py +181 -0
- aviato-0.3.0/tests/test_cli_release.py +256 -0
- aviato-0.3.0/tests/test_cli_repin_offboard.py +426 -0
- aviato-0.3.0/tests/test_cli_scan.py +234 -0
- aviato-0.3.0/tests/test_cli_sync.py +198 -0
- aviato-0.3.0/tests/test_cli_version_pin.py +64 -0
- aviato-0.3.0/tests/test_command_hardening.py +98 -0
- aviato-0.3.0/tests/test_docs_index.py +490 -0
- aviato-0.3.0/tests/test_docs_toolchain_parity.py +210 -0
- aviato-0.3.0/tests/test_github.py +497 -0
- aviato-0.3.0/tests/test_github_failclosed.py +38 -0
- aviato-0.3.0/tests/test_github_platform.py +1511 -0
- aviato-0.3.0/tests/test_library_source.py +110 -0
- aviato-0.3.0/tests/test_local_gate.py +134 -0
- aviato-0.3.0/tests/test_pipeline_privileges.py +160 -0
- aviato-0.3.0/tests/test_policy.py +51 -0
- aviato-0.3.0/tests/test_repos.py +27 -0
- aviato-0.3.0/tests/test_rulesets.py +307 -0
- aviato-0.3.0/tests/test_starter_governance.py +69 -0
- aviato-0.3.0/tests/test_validation.py +28 -0
- aviato-0.3.0/tests/test_validation_negative.py +612 -0
- aviato-0.3.0/tests/test_workflow_guards.py +1559 -0
aviato-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Mattas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
aviato-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aviato
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Reusable GitHub policy, CI, release, and onboarding conventions.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: PyYAML>=6.0
|
|
9
|
+
Requires-Dist: zizmor==1.25.2
|
|
10
|
+
Requires-Dist: bashlex==0.18
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: black==26.5.1; extra == "dev"
|
|
13
|
+
Requires-Dist: build==1.5.0; extra == "dev"
|
|
14
|
+
Requires-Dist: mypy==2.1.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest==9.0.3; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-cov==7.1.0; extra == "dev"
|
|
17
|
+
Requires-Dist: ruff==0.15.16; extra == "dev"
|
|
18
|
+
Requires-Dist: types-PyYAML==6.0.12.20260518; extra == "dev"
|
|
19
|
+
Requires-Dist: setuptools>=69; extra == "dev"
|
|
20
|
+
Requires-Dist: yamllint==1.35.1; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
aviato-0.3.0/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/aviato.jpg" alt="Aviato — Where code takes flight" width="520">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Aviato
|
|
6
|
+
|
|
7
|
+
Aviato is a reusable GitHub policy, CI, release, and onboarding conventions
|
|
8
|
+
library. It centralizes shared workflows, rulesets, and operator tooling without
|
|
9
|
+
keeping a committed inventory of consumer repositories.
|
|
10
|
+
|
|
11
|
+
## Policy
|
|
12
|
+
|
|
13
|
+
`aviato/library/policy.yml` is the canonical source for policy constants (it lives
|
|
14
|
+
inside the package so it ships in the wheel for installed ruleset rendering).
|
|
15
|
+
|
|
16
|
+
Release tags must match:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta)[0-9]+)?$
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Accepted examples: `1.2.3`, `1.2.3-alpha1`, `1.2.3-beta2`.
|
|
23
|
+
|
|
24
|
+
Rejected examples: `v1.2.3` (no `v` prefix allowed), `1.2.3-beta.1`,
|
|
25
|
+
`build-20260519.0215`.
|
|
26
|
+
|
|
27
|
+
Release publishing is tag-only. Legacy `release/*` branches should be cleaned up
|
|
28
|
+
in consumer repositories rather than supported by release publish workflows.
|
|
29
|
+
|
|
30
|
+
## What Is Here
|
|
31
|
+
|
|
32
|
+
- `aviato/library/policy.yml` - canonical policy constants (packaged; ships in the wheel).
|
|
33
|
+
- `aviato/library/rulesets.yml` - ruleset manifest.
|
|
34
|
+
- `aviato/library/rulesets/*.json` - GitHub ruleset templates.
|
|
35
|
+
- `.github/workflows/reusable-*.yml` - reusable CI, release, deploy, and security workflows.
|
|
36
|
+
- `templates/profile-*.yml`, `templates/consumer-automation.yml` - composed, copyable
|
|
37
|
+
caller-workflow examples for consumer repos (rendered from the scaffold bundles; they
|
|
38
|
+
include the always-on security baseline, §2.13). Use these composed callers rather than
|
|
39
|
+
hand-wiring a single reusable workflow, which can omit the required baseline.
|
|
40
|
+
- `aviato/` - Python CLI implementation.
|
|
41
|
+
- `scripts/*.sh` - compatibility wrappers and validation entrypoints.
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
Install locally:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python3 -m pip install -e .[dev]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Audit repositories under a local root:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
aviato audit .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Audit one repository:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
aviato audit --repo /Users/amattas/GitHub/example
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Dry-run the rulesets resolved from an adopted repository's declaration:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
aviato apply-rulesets amattas/example --declaration /path/to/example/.github/aviato.yaml
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Apply the reviewed declaration-resolved rulesets:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
aviato apply-rulesets amattas/example --declaration /path/to/example/.github/aviato.yaml --apply
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For a solo-maintainer repository, persist the liveness exception in its
|
|
76
|
+
declaration rather than relying on an ephemeral CLI flag:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
overrides:
|
|
80
|
+
settings:
|
|
81
|
+
default_branch:
|
|
82
|
+
required_reviews: 0
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then dry-run and apply using `--declaration` as above. Remove that override
|
|
86
|
+
before or in the same settings change that makes another reviewer eligible.
|
|
87
|
+
|
|
88
|
+
Validate this repository:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
./scripts/validate.sh
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Onboard or provision consumers with an explicit published Library pin:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
aviato onboard /path/to/repo --profile python-library --pin 1.2.3 --write
|
|
98
|
+
aviato onboard amattas/example --profile python-library --pin 1.2.3 --open-pr # adopt an existing repo via a reviewable scaffold PR
|
|
99
|
+
aviato provision amattas/example --profile node-service --pin 1.2.3
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Local mutation is always explicit. `onboard` plans by default; `--write` applies
|
|
103
|
+
the plan, `--allow-dirty` acknowledges a non-clean worktree, and
|
|
104
|
+
`--migrate-profile` authorizes changing an existing declaration's stable profile
|
|
105
|
+
identity. Repeat `--var KEY=VALUE` for non-secret profile variables. Provisioning
|
|
106
|
+
is private by default; add `--public` only when the new repository is intended to
|
|
107
|
+
be public. `--override-version-pin` is the exceptional recovery switch for a
|
|
108
|
+
recorded-pin mismatch; ordinary pin movement uses `aviato repin`.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
aviato onboard /path/to/repo --profile python-service --pin 1.2.3 --docs --var serve-pages=true --write
|
|
112
|
+
aviato onboard /path/to/repo --profile python-service --pin 1.2.3 --migrate-profile --allow-dirty --write
|
|
113
|
+
aviato provision OWNER/REPO --profile node-service --pin 1.2.3 --var project-name=app --var language-variant=typescript --public
|
|
114
|
+
aviato sync /path/to/repo --override-version-pin
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`--docs` on `onboard`/`provision` composes the opt-in docs deploy.
|
|
118
|
+
|
|
119
|
+
Fresh writes and provisioning refuse to invent a default pin. The requested pin
|
|
120
|
+
must already resolve to a published Aviato tag or branch; use
|
|
121
|
+
`--allow-unresolved-pin` only for intentional offline or test scaffolds. Already
|
|
122
|
+
adopted repositories keep their recorded pin during onboarding; use
|
|
123
|
+
`aviato repin` to move it.
|
|
124
|
+
|
|
125
|
+
Consumer lifecycle commands (see `aviato <command> --help` for the full flags):
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
aviato render-rulesets # print rendered ruleset JSON
|
|
129
|
+
aviato apply-rulesets OWNER/REPO --declaration /path/.github/aviato.yaml --apply # override-aware apply: resolves checks/approvals through the consumer's declared overrides (what settings-drift remediation recommends)
|
|
130
|
+
aviato doctor /path/to/consumer # classify managed artifacts + probe health
|
|
131
|
+
aviato sync /path/to/consumer # materialize managed artifacts incl. caller workflows
|
|
132
|
+
aviato scan /path/a /path/b [--fix --audit] # fleet diagnosis; --fix opens managed-file proposals, --audit surfaces open settings-drift tracking issues
|
|
133
|
+
aviato drift-report /path/to/consumer [--file-only|--settings-only] [--require-settings] # file + settings drift report
|
|
134
|
+
aviato reconcile /path/to/consumer ISSUE --confirm DIFF_ID # operator-gated settings apply, diff-bound
|
|
135
|
+
aviato complete-protection /path/to/consumer # idempotently (re-)apply full branch protection
|
|
136
|
+
aviato repin /path/to/consumer 1.3.0 [--write | --open-pr] # move the Library version pin; --open-pr opens a reviewable re-pin proposal
|
|
137
|
+
aviato offboard /path/to/consumer [--write --delete-files | --open-pr] # remove from Aviato management; --open-pr opens a reviewable removal proposal
|
|
138
|
+
aviato next-version --current 1.2.3 --commit "feat: x" # derive the next SemVer from Conventional Commits
|
|
139
|
+
aviato bump-version 1.3.0 /path/to/consumer # write the version into version-source locations
|
|
140
|
+
aviato is-highest 1.2.3 1.0.0 1.2.3 # exit 0 iff arg1 is the highest release among the rest
|
|
141
|
+
aviato lint-actions PATH # supply-chain pinning gate (zizmor + fail-closed curl|bash)
|
|
142
|
+
aviato validate # validate this repository's policy infrastructure (source checkout only)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`scan` is read-only unless `--fix` is supplied; `--fix` opens managed-file
|
|
146
|
+
proposals and `--audit` also reports the open settings-drift issue. `offboard
|
|
147
|
+
--write` removes Aviato's declaration/management state but preserves managed
|
|
148
|
+
files unless `--delete-files` is also supplied. If `doctor` reports missing or
|
|
149
|
+
corrupt seed integrity state, inspect the files first, then explicitly accept the
|
|
150
|
+
current seed-once contents as the new baseline:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
aviato scan /path/to/fleet --fix --audit
|
|
154
|
+
aviato offboard /path/to/consumer --write --delete-files
|
|
155
|
+
aviato sync /path/to/consumer --rebaseline-seeds
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Operator setup and live verification
|
|
159
|
+
|
|
160
|
+
Repository and service controls are deliberately operator-run. Apply the full
|
|
161
|
+
rulesets first; on GitHub plans that do not support tag metadata restrictions,
|
|
162
|
+
the command reports a loud degraded posture while retaining tag deletion and
|
|
163
|
+
non-fast-forward protection. Any unrelated API failure remains fatal.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
aviato apply-rulesets OWNER/REPO --apply --declaration /path/to/checkout/.github/aviato.yaml
|
|
167
|
+
gh api --paginate repos/OWNER/REPO/rulesets
|
|
168
|
+
# The list endpoint is only a summary. Fetch every full live payload for amattas/aviato:
|
|
169
|
+
for id in $(gh api --paginate repos/amattas/aviato/rulesets --jq '.[].id'); do
|
|
170
|
+
gh api "repos/amattas/aviato/rulesets/${id}"
|
|
171
|
+
done
|
|
172
|
+
gh api --method PUT repos/OWNER/REPO/automated-security-fixes
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
For PyPI or TestPyPI Trusted Publishing, register the consumer repository's
|
|
176
|
+
`.github/workflows/aviato-ci.yml` and protected `pypi` environment with the
|
|
177
|
+
project. The publishing job must remain in that consumer workflow so its OIDC
|
|
178
|
+
identity matches the registration; do not fall back to an API token.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Required reviewers are an operator prerequisite; configure at least one in protection.json.
|
|
182
|
+
gh api --method PUT repos/OWNER/REPO/environments/pypi --input protection.json
|
|
183
|
+
gh api repos/OWNER/REPO/environments/pypi
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
In the PyPI/TestPyPI project UI, enter exactly: owner `OWNER`, repository
|
|
187
|
+
`REPO`, workflow `aviato-ci.yml`, environment `pypi`. Registration is an
|
|
188
|
+
out-of-band service operation; Aviato never stores a publishing credential.
|
|
189
|
+
GitHub enforces the configured environment gate, and `aviato doctor` plus
|
|
190
|
+
operator verification checks the required-reviewer posture when applicable.
|
|
191
|
+
Only the App Store workflow has an additional explicit fail-closed runtime
|
|
192
|
+
preflight of the environment's reviewer list; the PyPI publisher does not.
|
|
193
|
+
|
|
194
|
+
Docs are always versioned onto `gh-pages` when `docs: true`. To serve them, set
|
|
195
|
+
the non-secret profile variable `serve-pages: true`, configure Pages for the
|
|
196
|
+
custom workflow build type, and verify the same release run contains a successful
|
|
197
|
+
job ID `deploy`, with display name `Deploy GitHub Pages`. That job invokes the
|
|
198
|
+
`actions/deploy-pages` action; `deploy-pages` is the action name, not Aviato's
|
|
199
|
+
job/check name:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
gh api --method PUT repos/OWNER/REPO/pages -f build_type=workflow
|
|
203
|
+
gh api repos/OWNER/REPO/pages
|
|
204
|
+
gh run list --repo OWNER/REPO --limit 20
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Before treating a rollout as live-verified, run the local gate and inspect the
|
|
208
|
+
exact external evidence (PR check/status SHA, full ruleset payloads, open CodeQL
|
|
209
|
+
alerts, package index entry, Pages URL, and App Store receipt release asset):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
AVIATO_STRICT_TOOLS=1 ./scripts/validate.sh
|
|
213
|
+
aviato doctor .
|
|
214
|
+
gh api --paginate repos/OWNER/REPO/code-scanning/alerts -f state=open -f tool_name=CodeQL --method GET
|
|
215
|
+
gh pr view PR_NUMBER --repo OWNER/REPO --json mergeable,mergeStateStatus,statusCheckRollup
|
|
216
|
+
gh release view RELEASE_TAG --repo OWNER/REPO --json url,assets
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The legacy script names still work:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
./scripts/audit-repos.sh .
|
|
223
|
+
./scripts/apply-rulesets.sh --repo amattas/example --declaration /path/to/example/.github/aviato.yaml --apply
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Reusable Workflows
|
|
227
|
+
|
|
228
|
+
- `reusable-python-ci.yml`
|
|
229
|
+
- `reusable-node-ci.yml`
|
|
230
|
+
- `reusable-swift-ci.yml`
|
|
231
|
+
- `reusable-release.yml`
|
|
232
|
+
- `reusable-release-gate.yml`
|
|
233
|
+
- `reusable-docker-ghcr.yml`
|
|
234
|
+
- `reusable-pypi-publish.yml`
|
|
235
|
+
- `reusable-docs-pages.yml`
|
|
236
|
+
- `reusable-app-store-connect.yml`
|
|
237
|
+
- `reusable-security-baseline.yml`
|
|
238
|
+
- `reusable-common-lint.yml`
|
|
239
|
+
- `reusable-consumer-automation.yml`
|
|
240
|
+
|
|
241
|
+
`reusable-release.yml` is split into a read-only derive job and a write release
|
|
242
|
+
job. `reusable-release-gate.yml` exports the validated commit as a `gated-sha`
|
|
243
|
+
output; the deploy workflows check out that exact commit — never the mutable
|
|
244
|
+
tag — and re-verify the tag still points at it before publishing. The GHCR
|
|
245
|
+
publish promotes the exact Trivy-scanned OCI archives by digest (no rebuild
|
|
246
|
+
between scan and push), and the PyPI/GHCR publish jobs run in GitHub deployment
|
|
247
|
+
environments (defaults `pypi`/`ghcr`).
|
|
248
|
+
|
|
249
|
+
Language CI workflows share the same command contract:
|
|
250
|
+
|
|
251
|
+
- `working-directory`
|
|
252
|
+
- `install-command`
|
|
253
|
+
- `lint-command`
|
|
254
|
+
- `test-command`
|
|
255
|
+
- `build-command`
|
|
256
|
+
- `run-install`
|
|
257
|
+
- `run-lint`
|
|
258
|
+
- `run-tests`
|
|
259
|
+
- `run-build`
|
|
260
|
+
|
|
261
|
+
`reusable-node-ci.yml` defaults to Node 24 and blocks npm versions below 11.10
|
|
262
|
+
(the floor for `min-release-age` support) before any install command. It sets
|
|
263
|
+
`ignore-scripts=true`, `engine-strict=true`, and `min-release-age=7`; Node
|
|
264
|
+
scaffolds also write those values into managed `.npmrc` files and declare
|
|
265
|
+
`node >=24` / `npm >=11.10` in package manifests so local and CI installs share
|
|
266
|
+
the same supply-chain posture. Node CI uses `npx --no-install` for local tool
|
|
267
|
+
bins, and the common lint gate rejects unsafe plain `npx` registry fetches.
|
|
268
|
+
|
|
269
|
+
## Profile Templates
|
|
270
|
+
|
|
271
|
+
The `templates/profile-*.yml` files compose CI, security, release validation,
|
|
272
|
+
and deployment jobs for common repo shapes:
|
|
273
|
+
|
|
274
|
+
- `profile-python-service.yml`
|
|
275
|
+
- `profile-python-library.yml`
|
|
276
|
+
- `profile-python-component.yml`
|
|
277
|
+
- `profile-node-service.yml`
|
|
278
|
+
- `profile-swift-app.yml`
|
|
279
|
+
|
|
280
|
+
These are **examples rendered from** the authoritative scaffold bundles
|
|
281
|
+
(`aviato/library/scaffold/files/wf-*.yml`); `aviato validate` fails if they drift,
|
|
282
|
+
and `python3 scripts/regen-templates.py` regenerates them. Prefer materializing the
|
|
283
|
+
real workflows with `aviato sync` / `aviato onboard --write`; committed examples
|
|
284
|
+
use the literal `EXAMPLE_PIN` placeholder instead of a production ref. Use
|
|
285
|
+
`aviato onboard TARGET --profile PROFILE` to list the exact artifacts, secrets,
|
|
286
|
+
rulesets, and protected deployment environments for a repository. The Python
|
|
287
|
+
library plan requires `pypi`, container profiles require `ghcr`, and the Swift
|
|
288
|
+
app plan requires the configured `environment-name` (`app-store-connect` by
|
|
289
|
+
default); each named environment must exist with at
|
|
290
|
+
least one required reviewer before deployment. Zero-deploy profiles explicitly
|
|
291
|
+
report that none are required. The common scaffold also seeds `CONTRIBUTING.md`,
|
|
292
|
+
`.github/CODEOWNERS`, and issue/PR templates (seed-once; the consumer owns them
|
|
293
|
+
after seeding).
|
|
294
|
+
|
|
295
|
+
When `docs: true`, Aviato scaffolds a **Zensical** docs site under `website/`,
|
|
296
|
+
versioned onto a docs branch by a mike fork (Mermaid rendering and sitemap
|
|
297
|
+
generation included; search is Zensical's built-in search, no external service).
|
|
298
|
+
The docs publish workflow installs, versions, builds, and publishes the site
|
|
299
|
+
on release refs;
|
|
300
|
+
`docs-retention` defaults to 0, which keeps every released version's docs.
|
|
301
|
+
|
|
302
|
+
The Library's own `.github/aviato.yaml` uses the internal `aviato-library`
|
|
303
|
+
profile and `bootstrap: true`. `local-install: true` is valid only for this
|
|
304
|
+
structural Library bootstrap path; reusable workflows fail before
|
|
305
|
+
`pip install -e .` if a consumer tries to enable it.
|
|
306
|
+
|
|
307
|
+
## App Store Connect
|
|
308
|
+
|
|
309
|
+
The App Store Connect workflow runs on release tags, uses macOS, and must be
|
|
310
|
+
called behind a protected deployment environment. Required secrets are:
|
|
311
|
+
|
|
312
|
+
- `APP_STORE_CONNECT_ISSUER_ID`
|
|
313
|
+
- `APP_STORE_CONNECT_KEY_ID`
|
|
314
|
+
- `APP_STORE_CONNECT_API_PRIVATE_KEY`
|
|
315
|
+
- `APPLE_CERTIFICATE_P12_BASE64`
|
|
316
|
+
- `APPLE_CERTIFICATE_PASSWORD`
|
|
317
|
+
- `APPLE_PROVISIONING_PROFILE_BASE64`
|
|
318
|
+
|
|
319
|
+
The caller must provide project-specific Xcode inputs such as scheme, workspace
|
|
320
|
+
or project, export options plist, and version/build-number command.
|
|
321
|
+
|
|
322
|
+
Apple/App Store Connect credentials are scoped to the specific workflow steps
|
|
323
|
+
that need them. The caller-controlled version command runs before signing assets
|
|
324
|
+
are installed, so Apple secrets are not exposed to arbitrary versioning logic.
|
|
325
|
+
|
|
326
|
+
## Architecture
|
|
327
|
+
|
|
328
|
+
See `docs/requirements/` for outcomes and constraints,
|
|
329
|
+
`docs/specifications/` for precise testable behavior, `docs/architecture/` for
|
|
330
|
+
the current implementation map, and `docs/security/` for threats and controls.
|
|
331
|
+
The § citation index remains `docs/requirements/README.md`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Aviato policy and repository tooling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import tomllib
|
|
7
|
+
from importlib import metadata
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
|
11
|
+
|
|
12
|
+
_SEMVER_RE = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:alpha|beta)[0-9]+)?$")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _source_version() -> str:
|
|
16
|
+
pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
17
|
+
project = tomllib.loads(pyproject.read_text(encoding="utf-8")).get("project")
|
|
18
|
+
version = project.get("version") if isinstance(project, dict) else None
|
|
19
|
+
if not isinstance(version, str):
|
|
20
|
+
raise RuntimeError(f"{pyproject} does not define project.version")
|
|
21
|
+
if _SEMVER_RE.fullmatch(version) is None:
|
|
22
|
+
raise RuntimeError(f"{pyproject} project.version is not valid SemVer: {version!r}")
|
|
23
|
+
return version
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _runtime_version() -> str:
|
|
27
|
+
try:
|
|
28
|
+
return metadata.version("aviato")
|
|
29
|
+
except metadata.PackageNotFoundError:
|
|
30
|
+
return _source_version()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__version__ = _runtime_version()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import asdict, dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from . import github
|
|
11
|
+
from .github import GitHubAPIError
|
|
12
|
+
from .policy import release_tag_pattern
|
|
13
|
+
from .repos import current_branch, discover_repos, normalize_slug, remote_url, tags, workflow_files
|
|
14
|
+
|
|
15
|
+
# R3-14/§5.9/§6.1: a bare floating-major tag (`1`, `2`) is a SANCTIONED release alias (the tag
|
|
16
|
+
# ruleset explicitly excludes it from the exact-SemVer pattern), so it is not an "invalid" tag.
|
|
17
|
+
_FLOATING_MAJOR_RE = re.compile(r"[0-9]+")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class AuditRow:
|
|
22
|
+
path: str
|
|
23
|
+
slug: str
|
|
24
|
+
default_branch: str
|
|
25
|
+
local_branch: str
|
|
26
|
+
workflows: str
|
|
27
|
+
default_branch_requires_pr: str
|
|
28
|
+
force_push_blocked: str
|
|
29
|
+
tag_ruleset: str
|
|
30
|
+
invalid_tags: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _requires_pr(rules: list[dict[str, Any]], protection: dict[str, Any]) -> bool:
|
|
34
|
+
rules_pr = any(rule.get("type") == "pull_request" for rule in rules)
|
|
35
|
+
classic_pr = (
|
|
36
|
+
"required_pull_request_reviews" in protection and protection.get("required_pull_request_reviews") is not None
|
|
37
|
+
)
|
|
38
|
+
return rules_pr or classic_pr
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _force_push_blocked(rules: list[dict[str, Any]], protection: dict[str, Any]) -> bool:
|
|
42
|
+
rules_nff = any(rule.get("type") == "non_fast_forward" for rule in rules)
|
|
43
|
+
allow_force_pushes = protection.get("allow_force_pushes")
|
|
44
|
+
classic_force_blocked = isinstance(allow_force_pushes, dict) and allow_force_pushes.get("enabled") is not True
|
|
45
|
+
return rules_nff or classic_force_blocked
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def audit_repo(repo: Path, *, root: Path, policy: dict[str, Any]) -> AuditRow:
|
|
49
|
+
pattern = re.compile(release_tag_pattern(policy))
|
|
50
|
+
repo_path = repo.resolve()
|
|
51
|
+
root_path = root.resolve()
|
|
52
|
+
rel = "." if repo_path == root_path else str(repo_path).removeprefix(str(root_path) + "/")
|
|
53
|
+
remote = remote_url(repo)
|
|
54
|
+
slug = normalize_slug(remote)
|
|
55
|
+
invalid_tags = ",".join(
|
|
56
|
+
tag for tag in tags(repo) if not pattern.fullmatch(tag) and not _FLOATING_MAJOR_RE.fullmatch(tag)
|
|
57
|
+
)[:500]
|
|
58
|
+
local_branch = current_branch(repo)
|
|
59
|
+
workflows = workflow_files(repo)
|
|
60
|
+
|
|
61
|
+
if not slug:
|
|
62
|
+
return AuditRow(rel, "", "", local_branch, workflows, "NO_REMOTE", "NO_REMOTE", "NO_REMOTE", invalid_tags)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
default = github.default_branch(slug)
|
|
66
|
+
except GitHubAPIError:
|
|
67
|
+
default = ""
|
|
68
|
+
|
|
69
|
+
if not default:
|
|
70
|
+
return AuditRow(rel, slug, "", local_branch, workflows, "API_ERROR", "API_ERROR", "API_ERROR", invalid_tags)
|
|
71
|
+
|
|
72
|
+
# These reads fail closed (§2.7): an ambiguous (auth/5xx/rate-limit) read raises
|
|
73
|
+
# rather than reporting a false "no protection". Degrade the whole row to
|
|
74
|
+
# API_ERROR rather than crash the audit, so one flaky repo doesn't sink the run.
|
|
75
|
+
try:
|
|
76
|
+
rules = github.active_branch_rules(slug, default)
|
|
77
|
+
protection = github.classic_branch_protection(slug, default)
|
|
78
|
+
tag_rulesets = github.tag_ruleset_names(slug)
|
|
79
|
+
except GitHubAPIError:
|
|
80
|
+
err = "API_ERROR"
|
|
81
|
+
return AuditRow(rel, slug, default, local_branch, workflows, err, err, err, invalid_tags)
|
|
82
|
+
|
|
83
|
+
return AuditRow(
|
|
84
|
+
path=rel,
|
|
85
|
+
slug=slug,
|
|
86
|
+
default_branch=default,
|
|
87
|
+
local_branch=local_branch,
|
|
88
|
+
workflows=workflows,
|
|
89
|
+
default_branch_requires_pr="yes" if _requires_pr(rules, protection) else "no",
|
|
90
|
+
force_push_blocked="yes" if _force_push_blocked(rules, protection) else "no",
|
|
91
|
+
tag_ruleset=",".join(tag_rulesets) if tag_rulesets else "no",
|
|
92
|
+
invalid_tags=invalid_tags,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def audit_repos(repos: Iterable[Path], *, root: Path, policy: dict[str, Any]) -> list[AuditRow]:
|
|
97
|
+
return [audit_repo(repo, root=root, policy=policy) for repo in repos]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def discover_and_audit(root: Path, *, policy: dict[str, Any]) -> list[AuditRow]:
|
|
101
|
+
return audit_repos(discover_repos(root), root=root, policy=policy)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def render_tsv(rows: list[AuditRow]) -> str:
|
|
105
|
+
fields = list(AuditRow.__dataclass_fields__.keys())
|
|
106
|
+
lines = ["\t".join(fields)]
|
|
107
|
+
for row in rows:
|
|
108
|
+
values = [str(getattr(row, field)) for field in fields]
|
|
109
|
+
lines.append("\t".join(values))
|
|
110
|
+
return "\n".join(lines)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def render_json(rows: list[AuditRow]) -> str:
|
|
114
|
+
return json.dumps([asdict(row) for row in rows], indent=2)
|