gaas-spec 0.1.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.
Files changed (31) hide show
  1. gaas_spec-0.1.0/.gitignore +71 -0
  2. gaas_spec-0.1.0/PKG-INFO +66 -0
  3. gaas_spec-0.1.0/README.md +42 -0
  4. gaas_spec-0.1.0/gaas_spec/__init__.py +30 -0
  5. gaas_spec-0.1.0/gaas_spec/bundle.py +220 -0
  6. gaas_spec-0.1.0/gaas_spec/cli.py +109 -0
  7. gaas_spec-0.1.0/gaas_spec/schemas/attestation.schema.json +77 -0
  8. gaas_spec-0.1.0/gaas_spec/schemas/audit.schema.json +108 -0
  9. gaas_spec-0.1.0/gaas_spec/schemas/auth.schema.json +232 -0
  10. gaas_spec-0.1.0/gaas_spec/schemas/escalation.schema.json +238 -0
  11. gaas_spec-0.1.0/gaas_spec/schemas/fieldpaths-0.1.json +25 -0
  12. gaas_spec-0.1.0/gaas_spec/schemas/policy.schema.json +241 -0
  13. gaas_spec-0.1.0/gaas_spec/signing.py +126 -0
  14. gaas_spec-0.1.0/pyproject.toml +43 -0
  15. gaas_spec-0.1.0/tests/fixtures/README.md +21 -0
  16. gaas_spec-0.1.0/tests/fixtures/expected.json +40 -0
  17. gaas_spec-0.1.0/tests/fixtures/invalid/bad-field-path/.gaas/policy.md +31 -0
  18. gaas_spec-0.1.0/tests/fixtures/invalid/bad-op/.gaas/policy.md +31 -0
  19. gaas_spec-0.1.0/tests/fixtures/invalid/missing-default-route/.gaas/escalation.md +13 -0
  20. gaas_spec-0.1.0/tests/fixtures/invalid/record-partial/.gaas/audit.md +17 -0
  21. gaas_spec-0.1.0/tests/fixtures/invalid/timeout-approve/.gaas/escalation.md +24 -0
  22. gaas_spec-0.1.0/tests/fixtures/invalid/unknown-top-key/.gaas/policy.md +32 -0
  23. gaas_spec-0.1.0/tests/fixtures/invalid/wrong-spec-version/.gaas/policy.md +31 -0
  24. gaas_spec-0.1.0/tests/fixtures/valid/full-bundle/.gaas/audit.md +17 -0
  25. gaas_spec-0.1.0/tests/fixtures/valid/full-bundle/.gaas/auth.md +21 -0
  26. gaas_spec-0.1.0/tests/fixtures/valid/full-bundle/.gaas/escalation.md +24 -0
  27. gaas_spec-0.1.0/tests/fixtures/valid/full-bundle/.gaas/policy.md +31 -0
  28. gaas_spec-0.1.0/tests/fixtures/valid/policy-only/.gaas/policy.md +31 -0
  29. gaas_spec-0.1.0/tests/fixtures/valid/when-always/.gaas/policy.md +13 -0
  30. gaas_spec-0.1.0/tests/test_conformance.py +38 -0
  31. gaas_spec-0.1.0/tests/test_signing.py +131 -0
@@ -0,0 +1,71 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .env.*
11
+ !.env.example
12
+
13
+ # IDE
14
+ .vscode/
15
+ .idea/
16
+ *.swp
17
+
18
+ # OS
19
+ .DS_Store
20
+ Thumbs.db
21
+ nul
22
+
23
+ # Claude Code (keep shared configs, ignore local/transient)
24
+ .claude/settings.local.json
25
+ .claude/history/
26
+ .claude/plans/
27
+ .claude/projects/
28
+ .claude/todos/
29
+
30
+ # Node / TypeScript
31
+ node_modules/
32
+ .vite/
33
+
34
+ # Project
35
+ *.log
36
+
37
+ # Generated reports
38
+ gaas-cost-audit.html
39
+
40
+ # Super admin credentials (contains passwords)
41
+ SUPER_ADMIN_CREDENTIALS.txt
42
+
43
+ # Local development scripts (not part of tracked codebase)
44
+ scripts/test-*.py
45
+ scripts/diagnose-*.py
46
+ scripts/send-*.py
47
+ scripts/trigger-*.py
48
+ scripts/uat-*.py
49
+ test_results_*.json
50
+ tests/e2e/
51
+
52
+ # Local trash can (never committed)
53
+ gaascan/
54
+
55
+ # Local planning docs (never committed)
56
+ .planning/
57
+
58
+ # Confidential business & sales documents
59
+ docs/business/
60
+ docs/sales/
61
+
62
+ # Session artifacts (Claude Code working docs)
63
+ /DAY_*.md
64
+ /DAYS_*.md
65
+ /HANDOFF_*.md
66
+ /think_tank.md
67
+
68
+ # Auto-generated test reports
69
+ apps/dashboard/playwright-report/
70
+ apps/dashboard/test-results/
71
+ apps/dashboard/e2e/screenshots/
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: gaas-spec
3
+ Version: 0.1.0
4
+ Summary: Reference validator, signer, and verifier for the auth.md governance file convention (spec 0.1)
5
+ Project-URL: Homepage, https://github.com/H2OmAI/authmd
6
+ Project-URL: Specification, https://github.com/H2OmAI/authmd
7
+ Project-URL: Repository, https://github.com/H2OmAI/gaas
8
+ Author-email: H2Om <sdk@gaas.is>
9
+ License-Expression: Apache-2.0
10
+ Keywords: agents,ai,auth.md,gaas,governance,policy-as-code
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: cryptography>=43.0.0
19
+ Requires-Dist: jsonschema>=4.21.0
20
+ Requires-Dist: pyyaml>=6.0.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # gaas-spec
26
+
27
+ Reference validator, signer, and verifier for the **auth.md governance file convention** — spec 0.1.
28
+
29
+ The convention: a `.gaas/` directory (`auth.md`, `policy.md`, `audit.md`, `escalation.md`, `attestation.sig`) declaring how an AI agent is governed — readable by people, validated and enforced by machines, signed like a release. Spec, schemas, and conformance fixtures: **[github.com/H2OmAI/authmd](https://github.com/H2OmAI/authmd)**.
30
+
31
+ Local-only: no network, no API key. Suitable for CI.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install gaas-spec
37
+ ```
38
+
39
+ ## CLI
40
+
41
+ ```bash
42
+ gaas validate # validate ./.gaas (or: gaas validate path/to/repo)
43
+ gaas sign --key org-key.pem --key-id acme-2026
44
+ gaas verify --pub acme-2026=org-pub.pem
45
+ ```
46
+
47
+ `validate` exits non-zero with findings when the bundle doesn't conform — wire it straight into CI. `sign` refuses non-conforming bundles. `verify` checks every file hash against the attestation manifest before checking signatures, so a tampered bundle fails loudly.
48
+
49
+ ## Library
50
+
51
+ ```python
52
+ from gaas_spec import parse_bundle, validate, sign, verify
53
+
54
+ bundle = parse_bundle(".") # finds ./.gaas
55
+ findings = validate(bundle) # [] means conforming
56
+ attestation = sign(bundle, private_key_pem, key_id="acme-2026", signer="org")
57
+ results = verify(bundle, attestation, {"acme-2026": public_key_pem})
58
+ ```
59
+
60
+ ## Conformance
61
+
62
+ This package vendors the spec repo's fixtures and runs them in its test suite — it is the reference implementation and must agree with the fixtures at all times. Signing is ES256 (ECDSA P-256 + SHA-256) over an RFC 8785 canonical-JSON manifest hash, per SPEC-attestation.md.
63
+
64
+ ## License
65
+
66
+ Apache-2.0. The spec text itself is CC BY 4.0 at the spec repo.
@@ -0,0 +1,42 @@
1
+ # gaas-spec
2
+
3
+ Reference validator, signer, and verifier for the **auth.md governance file convention** — spec 0.1.
4
+
5
+ The convention: a `.gaas/` directory (`auth.md`, `policy.md`, `audit.md`, `escalation.md`, `attestation.sig`) declaring how an AI agent is governed — readable by people, validated and enforced by machines, signed like a release. Spec, schemas, and conformance fixtures: **[github.com/H2OmAI/authmd](https://github.com/H2OmAI/authmd)**.
6
+
7
+ Local-only: no network, no API key. Suitable for CI.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install gaas-spec
13
+ ```
14
+
15
+ ## CLI
16
+
17
+ ```bash
18
+ gaas validate # validate ./.gaas (or: gaas validate path/to/repo)
19
+ gaas sign --key org-key.pem --key-id acme-2026
20
+ gaas verify --pub acme-2026=org-pub.pem
21
+ ```
22
+
23
+ `validate` exits non-zero with findings when the bundle doesn't conform — wire it straight into CI. `sign` refuses non-conforming bundles. `verify` checks every file hash against the attestation manifest before checking signatures, so a tampered bundle fails loudly.
24
+
25
+ ## Library
26
+
27
+ ```python
28
+ from gaas_spec import parse_bundle, validate, sign, verify
29
+
30
+ bundle = parse_bundle(".") # finds ./.gaas
31
+ findings = validate(bundle) # [] means conforming
32
+ attestation = sign(bundle, private_key_pem, key_id="acme-2026", signer="org")
33
+ results = verify(bundle, attestation, {"acme-2026": public_key_pem})
34
+ ```
35
+
36
+ ## Conformance
37
+
38
+ This package vendors the spec repo's fixtures and runs them in its test suite — it is the reference implementation and must agree with the fixtures at all times. Signing is ES256 (ECDSA P-256 + SHA-256) over an RFC 8785 canonical-JSON manifest hash, per SPEC-attestation.md.
39
+
40
+ ## License
41
+
42
+ Apache-2.0. The spec text itself is CC BY 4.0 at the spec repo.
@@ -0,0 +1,30 @@
1
+ """gaas-spec — reference validator/signer/verifier for the auth.md convention.
2
+
3
+ Spec: https://github.com/H2OmAI/authmd (version 0.1)
4
+ """
5
+
6
+ from .bundle import (
7
+ BUNDLE_FILES,
8
+ SPEC_VERSIONS,
9
+ Finding,
10
+ GovernanceBundle,
11
+ parse_bundle,
12
+ validate,
13
+ )
14
+ from .signing import canonical_manifest, manifest_hash, sign, verify
15
+
16
+ __version__ = "0.1.0"
17
+
18
+ __all__ = [
19
+ "BUNDLE_FILES",
20
+ "SPEC_VERSIONS",
21
+ "Finding",
22
+ "GovernanceBundle",
23
+ "parse_bundle",
24
+ "validate",
25
+ "canonical_manifest",
26
+ "manifest_hash",
27
+ "sign",
28
+ "verify",
29
+ "__version__",
30
+ ]
@@ -0,0 +1,220 @@
1
+ """Parse and validate .gaas/ governance bundles (spec 0.1).
2
+
3
+ The validator is deliberately strict: schema validation plus the semantic
4
+ checks the spec requires beyond JSON Schema (default escalation route,
5
+ timeout-chain termination, matcher depth/leaf limits, spec version).
6
+ Fixtures in the spec repo are ground truth.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ from dataclasses import dataclass, field
14
+ from importlib import resources
15
+ from pathlib import Path
16
+
17
+ import yaml
18
+ from jsonschema import Draft202012Validator
19
+
20
+ SPEC_VERSIONS = ("0.1",)
21
+ BUNDLE_FILES = ("auth.md", "policy.md", "audit.md", "escalation.md")
22
+ MAX_MATCHER_DEPTH = 8
23
+ MAX_MATCHER_LEAVES = 64
24
+
25
+ _SCHEMAS: dict[str, Draft202012Validator] = {}
26
+
27
+
28
+ def _schema(fname: str) -> Draft202012Validator:
29
+ if fname not in _SCHEMAS:
30
+ ref = resources.files("gaas_spec.schemas") / f"{fname.removesuffix('.md')}.schema.json"
31
+ _SCHEMAS[fname] = Draft202012Validator(json.loads(ref.read_text()))
32
+ return _SCHEMAS[fname]
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class Finding:
37
+ """A single validation finding."""
38
+
39
+ file: str
40
+ code: str
41
+ message: str
42
+
43
+ def __str__(self) -> str: # pragma: no cover - cosmetic
44
+ return f"{self.file}: [{self.code}] {self.message}"
45
+
46
+
47
+ @dataclass
48
+ class GovernanceBundle:
49
+ """A parsed .gaas/ bundle: raw bytes, frontmatter, and file hashes."""
50
+
51
+ root: Path
52
+ files: dict[str, bytes] = field(default_factory=dict)
53
+ frontmatter: dict[str, dict] = field(default_factory=dict)
54
+ parse_findings: list[Finding] = field(default_factory=list)
55
+
56
+ @property
57
+ def hashes(self) -> dict[str, str]:
58
+ return {
59
+ name: f"sha256:{hashlib.sha256(raw).hexdigest()}"
60
+ for name, raw in sorted(self.files.items())
61
+ }
62
+
63
+
64
+ def parse_bundle(path: str | Path) -> GovernanceBundle:
65
+ """Parse a bundle directory (either the .gaas/ dir or its parent)."""
66
+ root = Path(path)
67
+ if (root / ".gaas").is_dir():
68
+ root = root / ".gaas"
69
+ bundle = GovernanceBundle(root=root)
70
+ if not root.is_dir():
71
+ bundle.parse_findings.append(
72
+ Finding("<bundle>", "bundle-not-found", f"no such directory: {root}")
73
+ )
74
+ return bundle
75
+
76
+ for name in BUNDLE_FILES:
77
+ f = root / name
78
+ if not f.is_file():
79
+ continue
80
+ raw = f.read_bytes()
81
+ bundle.files[name] = raw
82
+ text = raw.decode("utf-8", errors="strict")
83
+ if not text.startswith("---\n") or "\n---" not in text[4:]:
84
+ bundle.parse_findings.append(
85
+ Finding(name, "missing-frontmatter", "file must start with a YAML frontmatter block")
86
+ )
87
+ continue
88
+ end = text.index("\n---", 4)
89
+ try:
90
+ fm = yaml.safe_load(text[4:end])
91
+ except yaml.YAMLError as exc:
92
+ bundle.parse_findings.append(Finding(name, "invalid-yaml", str(exc)[:200]))
93
+ continue
94
+ if not isinstance(fm, dict):
95
+ bundle.parse_findings.append(
96
+ Finding(name, "invalid-frontmatter", "frontmatter must be a mapping")
97
+ )
98
+ continue
99
+ bundle.frontmatter[name] = fm
100
+
101
+ if not bundle.files:
102
+ bundle.parse_findings.append(
103
+ Finding("<bundle>", "empty-bundle", f"no governance files found in {root}")
104
+ )
105
+ return bundle
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Validation
110
+ # ---------------------------------------------------------------------------
111
+
112
+
113
+ def _matcher_limits(node: object, depth: int = 1) -> tuple[int, int]:
114
+ """Return (max_depth, leaf_count) of a matcher tree."""
115
+ if not isinstance(node, dict):
116
+ return depth, 0
117
+ if "field" in node:
118
+ return depth, 1
119
+ children: list[object] = []
120
+ for key in ("all", "any"):
121
+ if key in node and isinstance(node[key], list):
122
+ children.extend(node[key])
123
+ if "not" in node:
124
+ children.append(node["not"])
125
+ max_d, leaves = depth, 0
126
+ for child in children:
127
+ d, n = _matcher_limits(child, depth + 1)
128
+ max_d = max(max_d, d)
129
+ leaves += n
130
+ return max_d, leaves
131
+
132
+
133
+ def _iter_matchers(fname: str, fm: dict):
134
+ if fname == "policy.md":
135
+ for pol in fm.get("policies", []):
136
+ when = pol.get("when")
137
+ if isinstance(when, dict):
138
+ yield when
139
+ elif fname == "auth.md":
140
+ for entry in fm.get("requires_human", []):
141
+ when = entry.get("when")
142
+ if isinstance(when, dict):
143
+ yield when
144
+ elif fname == "escalation.md":
145
+ for route in fm.get("routes", []):
146
+ match = route.get("match")
147
+ if isinstance(match, dict):
148
+ yield match
149
+
150
+
151
+ def _semantic_findings(fname: str, fm: dict) -> list[Finding]:
152
+ findings: list[Finding] = []
153
+
154
+ spec = fm.get("gaas_spec")
155
+ if spec not in SPEC_VERSIONS:
156
+ findings.append(
157
+ Finding(
158
+ fname,
159
+ "unsupported-spec-version",
160
+ f"gaas_spec {spec!r} not implemented (supported: {', '.join(SPEC_VERSIONS)})",
161
+ )
162
+ )
163
+
164
+ for tree in _iter_matchers(fname, fm):
165
+ depth, leaves = _matcher_limits(tree)
166
+ if depth > MAX_MATCHER_DEPTH:
167
+ findings.append(
168
+ Finding(fname, "matcher-too-deep", f"depth {depth} > {MAX_MATCHER_DEPTH}")
169
+ )
170
+ if leaves > MAX_MATCHER_LEAVES:
171
+ findings.append(
172
+ Finding(fname, "matcher-too-large", f"{leaves} leaves > {MAX_MATCHER_LEAVES}")
173
+ )
174
+
175
+ if fname == "escalation.md":
176
+ routes = {r.get("id"): r for r in fm.get("routes", []) if isinstance(r, dict)}
177
+ if "default" not in routes:
178
+ findings.append(
179
+ Finding(fname, "missing-default-route", "a route with id 'default' is required")
180
+ )
181
+ for rid, route in routes.items():
182
+ seen: set[str] = set()
183
+ cur: str | None = rid
184
+ while cur is not None:
185
+ if cur in seen:
186
+ findings.append(
187
+ Finding(fname, "escalation-cycle", f"timeout chain from {rid!r} cycles")
188
+ )
189
+ break
190
+ seen.add(cur)
191
+ r = routes.get(cur)
192
+ if r is None:
193
+ findings.append(
194
+ Finding(fname, "dangling-escalate-to", f"route {cur!r} does not exist")
195
+ )
196
+ break
197
+ cur = r.get("escalate_to") if r.get("on_timeout") == "escalate_up" else None
198
+
199
+ return findings
200
+
201
+
202
+ def validate(bundle: GovernanceBundle) -> list[Finding]:
203
+ """Validate a parsed bundle. Empty list = conforming."""
204
+ findings = list(bundle.parse_findings)
205
+
206
+ orgs = set()
207
+ for fname, fm in bundle.frontmatter.items():
208
+ for err in _schema(fname).iter_errors(fm):
209
+ path = ".".join(str(p) for p in err.absolute_path) or "<root>"
210
+ findings.append(Finding(fname, "schema", f"{path}: {err.message[:160]}"))
211
+ findings.extend(_semantic_findings(fname, fm))
212
+ if isinstance(fm.get("org"), str):
213
+ orgs.add(fm["org"])
214
+
215
+ if len(orgs) > 1:
216
+ findings.append(
217
+ Finding("<bundle>", "org-mismatch", f"files declare different orgs: {sorted(orgs)}")
218
+ )
219
+
220
+ return findings
@@ -0,0 +1,109 @@
1
+ """gaas — CLI for the auth.md governance file convention (spec 0.1).
2
+
3
+ Local-only: no network, no API key. Suitable for CI.
4
+
5
+ gaas validate [path] # validate a .gaas/ bundle (default: cwd)
6
+ gaas sign [path] --key k.pem --key-id my-key [--signer org]
7
+ gaas verify [path] --pub key_id=pub.pem [--pub ...]
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ from .bundle import parse_bundle, validate
18
+ from .signing import sign, verify
19
+
20
+
21
+ def _cmd_validate(args: argparse.Namespace) -> int:
22
+ bundle = parse_bundle(args.path)
23
+ findings = validate(bundle)
24
+ for f in findings:
25
+ print(f" ✗ {f}", file=sys.stderr)
26
+ if findings:
27
+ print(f"\n{len(findings)} finding(s) — bundle does NOT conform to spec 0.1", file=sys.stderr)
28
+ return 1
29
+ print(f"✓ {len(bundle.files)} file(s) conform to spec 0.1: {', '.join(sorted(bundle.files))}")
30
+ return 0
31
+
32
+
33
+ def _cmd_sign(args: argparse.Namespace) -> int:
34
+ bundle = parse_bundle(args.path)
35
+ findings = validate(bundle)
36
+ if findings:
37
+ print("refusing to sign a non-conforming bundle:", file=sys.stderr)
38
+ for f in findings:
39
+ print(f" ✗ {f}", file=sys.stderr)
40
+ return 1
41
+ attestation = sign(
42
+ bundle,
43
+ Path(args.key).read_bytes(),
44
+ key_id=args.key_id,
45
+ signer=args.signer,
46
+ )
47
+ out = bundle.root / "attestation.sig"
48
+ out.write_text(json.dumps(attestation, indent=2) + "\n")
49
+ print(f"✓ wrote {out} (manifest_hash {attestation['manifest_hash'][:23]}…)")
50
+ return 0
51
+
52
+
53
+ def _cmd_verify(args: argparse.Namespace) -> int:
54
+ bundle = parse_bundle(args.path)
55
+ att_path = bundle.root / "attestation.sig"
56
+ if not att_path.is_file():
57
+ print(f"✗ no attestation.sig in {bundle.root}", file=sys.stderr)
58
+ return 1
59
+ attestation = json.loads(att_path.read_text())
60
+ keys: dict[str, bytes] = {}
61
+ for spec in args.pub or []:
62
+ key_id, _, pem_path = spec.partition("=")
63
+ if not pem_path:
64
+ print(f"✗ --pub must be key_id=path.pem (got {spec!r})", file=sys.stderr)
65
+ return 2
66
+ keys[key_id] = Path(pem_path).read_bytes()
67
+ try:
68
+ results = verify(bundle, attestation, keys)
69
+ except ValueError as exc:
70
+ print(f"✗ {exc}", file=sys.stderr)
71
+ return 1
72
+ any_valid = False
73
+ for key_id, status in results.items():
74
+ mark = "✓" if status == "valid" else "✗"
75
+ print(f" {mark} {key_id}: {status}")
76
+ any_valid |= status == "valid"
77
+ if not any_valid:
78
+ print("✗ no valid signature", file=sys.stderr)
79
+ return 1
80
+ print("✓ bundle verified")
81
+ return 0
82
+
83
+
84
+ def main(argv: list[str] | None = None) -> int:
85
+ parser = argparse.ArgumentParser(prog="gaas", description=__doc__)
86
+ sub = parser.add_subparsers(dest="command", required=True)
87
+
88
+ p = sub.add_parser("validate", help="validate a .gaas/ bundle")
89
+ p.add_argument("path", nargs="?", default=".")
90
+ p.set_defaults(func=_cmd_validate)
91
+
92
+ p = sub.add_parser("sign", help="sign a bundle -> attestation.sig")
93
+ p.add_argument("path", nargs="?", default=".")
94
+ p.add_argument("--key", required=True, help="ECDSA P-256 private key (PEM)")
95
+ p.add_argument("--key-id", required=True)
96
+ p.add_argument("--signer", choices=["org", "runtime"], default="org")
97
+ p.set_defaults(func=_cmd_sign)
98
+
99
+ p = sub.add_parser("verify", help="verify attestation.sig against the bundle")
100
+ p.add_argument("path", nargs="?", default=".")
101
+ p.add_argument("--pub", action="append", help="key_id=public.pem (repeatable)")
102
+ p.set_defaults(func=_cmd_verify)
103
+
104
+ args = parser.parse_args(argv)
105
+ return args.func(args)
106
+
107
+
108
+ if __name__ == "__main__": # pragma: no cover
109
+ sys.exit(main())
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://gaas.to/spec/0.1/attestation.schema.json",
4
+ "title": "attestation.sig \u2014 spec 0.1",
5
+ "type": "object",
6
+ "$defs": {},
7
+ "properties": {
8
+ "gaas_spec": {
9
+ "const": "0.1"
10
+ },
11
+ "org": {
12
+ "type": "string",
13
+ "pattern": "^[a-z0-9_-]{1,64}$"
14
+ },
15
+ "manifest": {
16
+ "type": "object",
17
+ "patternProperties": {
18
+ "^(auth|policy|audit|escalation)\\.md$": {
19
+ "type": "string",
20
+ "pattern": "^sha256:[0-9a-f]{64}$"
21
+ }
22
+ },
23
+ "minProperties": 1,
24
+ "additionalProperties": false
25
+ },
26
+ "manifest_hash": {
27
+ "type": "string",
28
+ "pattern": "^sha256:[0-9a-f]{64}$"
29
+ },
30
+ "algo": {
31
+ "const": "ES256"
32
+ },
33
+ "signatures": {
34
+ "type": "array",
35
+ "minItems": 1,
36
+ "items": {
37
+ "type": "object",
38
+ "properties": {
39
+ "key_id": {
40
+ "type": "string",
41
+ "minLength": 1
42
+ },
43
+ "signer": {
44
+ "enum": [
45
+ "org",
46
+ "runtime"
47
+ ]
48
+ },
49
+ "sig": {
50
+ "type": "string",
51
+ "minLength": 1
52
+ },
53
+ "signed_at": {
54
+ "type": "string",
55
+ "format": "date-time"
56
+ }
57
+ },
58
+ "required": [
59
+ "key_id",
60
+ "signer",
61
+ "sig",
62
+ "signed_at"
63
+ ],
64
+ "additionalProperties": false
65
+ }
66
+ }
67
+ },
68
+ "required": [
69
+ "gaas_spec",
70
+ "org",
71
+ "manifest",
72
+ "manifest_hash",
73
+ "algo",
74
+ "signatures"
75
+ ],
76
+ "additionalProperties": false
77
+ }