test: gate releases on enforced conformance and installed package checks
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
a15fe032b0
commit
1ece969f59
6 changed files with 206 additions and 17 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# CI smoke — package tests prove hub-core library health on push.
|
||||
# Local source, enforcement, inventory and installed-package gates.
|
||||
name: CI Smoke
|
||||
|
||||
on:
|
||||
|
|
@ -22,23 +22,21 @@ jobs:
|
|||
pytest-smoke:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Run hub-core pytest
|
||||
- name: Test enforcement, inventory and installed package
|
||||
run: |
|
||||
set -eu
|
||||
REF="${GITHUB_SHA:-main}"
|
||||
SHORT="${REF:0:7}"
|
||||
ROOT="${HOME}/ci-hub-core-${SHORT}"
|
||||
rm -rf "${ROOT}"
|
||||
mkdir -p "${ROOT}"
|
||||
wget -qO /tmp/hub-core.tar.gz \
|
||||
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz"
|
||||
tar xzf /tmp/hub-core.tar.gz -C "${ROOT}" --strip-components=1
|
||||
cd "${ROOT}"
|
||||
REF="${GITHUB_SHA:?commit SHA required}"
|
||||
CI_WORKDIR="$(mktemp -d)"
|
||||
trap 'rm -rf -- "$CI_WORKDIR"' EXIT
|
||||
wget -qO "$CI_WORKDIR/source.tar.gz" \
|
||||
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${REF}.tar.gz"
|
||||
mkdir "$CI_WORKDIR/source"
|
||||
tar xzf "$CI_WORKDIR/source.tar.gz" -C "$CI_WORKDIR/source" --strip-components=1
|
||||
cd "$CI_WORKDIR/source"
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
pip install --user uv
|
||||
export PATH="${HOME}/.local/bin:${PATH}"
|
||||
fi
|
||||
uv sync
|
||||
uv run python -c "import hub_core; print(hub_core.__version__)"
|
||||
uv run python -m pytest -q
|
||||
echo "hub-core pytest smoke ok @ ${SHORT}"
|
||||
uv sync --locked --group dev --extra runtime
|
||||
make ci-check
|
||||
echo "hub-core source, enforcement, inventory and package checks passed @ ${REF}"
|
||||
|
|
|
|||
14
Makefile
14
Makefile
|
|
@ -1,4 +1,4 @@
|
|||
.PHONY: install test runtime-run conformance container-build ecosystem-regression
|
||||
.PHONY: install test runtime-run conformance container-build ecosystem-regression inventory-check package-check ci-check
|
||||
|
||||
UV ?= uv
|
||||
ECOSYSTEM_REGRESSION ?= /home/worsch/the-custodian/scripts/hub-ecosystem-regression.sh
|
||||
|
|
@ -9,7 +9,7 @@ install:
|
|||
$(UV) sync
|
||||
|
||||
test:
|
||||
$(UV) run python -m pytest -q
|
||||
$(UV) run --locked --extra runtime python -m pytest -q
|
||||
|
||||
runtime-run:
|
||||
$(UV) run --extra runtime hub-core api
|
||||
|
|
@ -22,3 +22,13 @@ container-build:
|
|||
|
||||
ecosystem-regression:
|
||||
bash $(ECOSYSTEM_REGRESSION)
|
||||
|
||||
# Local equivalents of the required CI gates.
|
||||
inventory-check:
|
||||
$(UV) run --locked --extra runtime python tools/build_access_inventory.py --inventory docs/platform-access-inventory.json --check
|
||||
|
||||
package-check:
|
||||
$(UV) build --out-dir dist/ci
|
||||
@set -eu; set -- dist/ci/*.whl; test "$$#" -eq 1; $(UV) run --no-project --with "$$1" python -I tools/check_installed_package.py
|
||||
|
||||
ci-check: test inventory-check package-check
|
||||
|
|
|
|||
|
|
@ -46,3 +46,27 @@ absorption slices that are not part of the T04 minimal vertical. Tenant
|
|||
isolation also remains open because the 0.1 runtime has no tenant identity or
|
||||
authorization context yet. These gaps must not be interpreted as passing; the
|
||||
harness reports only the implemented profile above.
|
||||
|
||||
## Access enforcement and CI gates
|
||||
|
||||
`make ci-check` runs the test suite, checks the reviewed access inventory for
|
||||
source drift, builds distributions and validates an installed wheel outside the
|
||||
checkout's import path. Forgejo runs these gates for `main` pushes and manual
|
||||
runs, using the full commit SHA and a unique temporary checkout. CI installs the
|
||||
locked development and runtime dependencies first. Individual gates are
|
||||
`make test`, `make inventory-check` and `make package-check`.
|
||||
|
||||
`tests/test_enforced_conformance.py` runs all twelve existing Tier 2/3 checks
|
||||
through an explicitly enforced runtime using a real signed IAM JWT and synthetic
|
||||
owner facts, policy and audit. It verifies event attribution against authorization
|
||||
records. Additional journeys establish valid state, deny both reads and writes
|
||||
for anonymous/invalid credentials, revoked entitlement, policy denial and
|
||||
policy/audit outages, then independently read back unchanged stored messages.
|
||||
These tests run in the ordinary suite; they need no external owner checkout.
|
||||
|
||||
The installed-wheel gate validates runtime/security imports, packaged action and
|
||||
browser route coverage, contract fixtures/schemas and the migration template.
|
||||
It makes no owner requests and starts no service. The separate optional Audit
|
||||
Core interoperability suite still requires `HUB_CORE_AUDIT_CORE_SOURCE` and is
|
||||
not silently represented as covered by ordinary CI. Local CI-equivalent success
|
||||
is not a deployed Forgejo receipt or live owner/platform acceptance.
|
||||
|
|
|
|||
89
tests/test_enforced_conformance.py
Normal file
89
tests/test_enforced_conformance.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""Disposable enforcement-mode journeys; synthetic owners, real signed IAM token.
|
||||
|
||||
These exercise the complete Tier 2/3 workload through AccessBoundary. They do
|
||||
not establish issuer registration, deployed custody or platform acceptance.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from hub_core.conformance import ConformanceHarness
|
||||
from hub_core.runtime.app import create_app
|
||||
from hub_core.runtime.config import RuntimeSettings
|
||||
from test_access_boundary import Owners
|
||||
from test_access_identity import setup
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def signing_key():
|
||||
return rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def enforced(signing_key):
|
||||
token, identity, _, upstream = setup(signing_key)
|
||||
owners = Owners()
|
||||
owners.facts = replace(owners.facts, producer_addresses=frozenset({'hub:ops-hub'}))
|
||||
async def current_facts(actor, resource):
|
||||
return replace(owners.facts, checked_at=time.time())
|
||||
owners.resolve = current_facts
|
||||
controller = owners.controller()
|
||||
controller.identity = identity
|
||||
app = create_app(settings=RuntimeSettings(environment='test', access_mode='enforce'),
|
||||
access_controller=controller)
|
||||
with TestClient(app, base_url='https://hub.example', headers={'Authorization': 'Bearer ' + token}) as client:
|
||||
yield client, owners
|
||||
asyncio.run(upstream.aclose())
|
||||
|
||||
|
||||
def test_tier_2_and_3_workload_passes_through_enforcement(enforced):
|
||||
client, owners = enforced
|
||||
report = ConformanceHarness(client).run()
|
||||
assert report.passed, report.to_dict()
|
||||
assert report.passed_count == 12
|
||||
assert owners.requests
|
||||
assert all(r.actor.subject == 'immutable-root' for r in owners.requests)
|
||||
records = {r['correlation_id']: r for r in owners.records if r['outcome'] == 'authorized'}
|
||||
for family in ('progress', 'interaction'):
|
||||
result = client.get('/ports/projections/' + family + '_events')
|
||||
event = result.json()['data']['items'][0]
|
||||
attribution = event['payload']['_hub_access']
|
||||
record = records[attribution['correlation_id']]
|
||||
assert record['subject'] == 'immutable-root'
|
||||
assert 'Projection rebuild conformance fixture.' not in json.dumps(owners.records)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure,status', [('anonymous',401), ('invalid',401),
|
||||
('revoked',403), ('policy_denied',403), ('policy_outage',503), ('audit_outage',503)])
|
||||
def test_denied_reads_and_writes_leave_no_business_mutation(enforced, failure, status):
|
||||
client, owners = enforced
|
||||
assert ConformanceHarness(client).run().passed
|
||||
before = client.get('/ports/projections/messages').json()['data']['items']
|
||||
saved = client.headers['authorization']
|
||||
if failure == 'anonymous':
|
||||
del client.headers['authorization']
|
||||
elif failure == 'invalid':
|
||||
client.headers['authorization'] = 'Bearer invalid'
|
||||
elif failure == 'revoked':
|
||||
owners.facts = replace(owners.facts, root_entitled=False)
|
||||
elif failure == 'policy_denied':
|
||||
owners.allow = False
|
||||
elif failure == 'policy_outage':
|
||||
owners.policy_down = True
|
||||
else:
|
||||
owners.audit_down = True
|
||||
message = {'schema_version':'0.1.0', 'correlation_id':str(uuid4()),
|
||||
'from_address':'hub:ops-hub', 'to_addresses':['agent:conformance'],
|
||||
'body':'must never commit'}
|
||||
assert client.get('/ports/projections/messages').status_code == status
|
||||
assert client.post('/ports/messaging/messages', json=message).status_code == status
|
||||
client.headers['authorization'] = saved
|
||||
owners.facts = replace(owners.facts, root_entitled=True)
|
||||
owners.allow, owners.policy_down, owners.audit_down = True, False, False
|
||||
assert client.get('/ports/projections/messages').json()['data']['items'] == before
|
||||
46
tools/check_installed_package.py
Normal file
46
tools/check_installed_package.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check an installed wheel outside the checkout's import path (use python -I).
|
||||
|
||||
Run via: uv run --no-project --with dist/<wheel> python -I tools/check_installed_package.py
|
||||
No startup, owner requests, credentials or database connection are needed.
|
||||
"""
|
||||
import json
|
||||
from importlib.resources import files
|
||||
from pathlib import Path
|
||||
|
||||
import hub_core
|
||||
from hub_core.conformance import ConformanceHarness
|
||||
from hub_core.runtime.app import create_app
|
||||
from hub_core.runtime.config import RuntimeSettings
|
||||
from hub_core.runtime.inbox_projection import create_inbox_projection_router
|
||||
from hub_core.security.boundary import iter_routes, route_key
|
||||
from hub_core.security.browser import BROWSER_ROUTES, create_browser_router
|
||||
|
||||
|
||||
def main():
|
||||
checkout = Path(__file__).resolve().parents[1]
|
||||
installed = Path(hub_core.__file__).resolve()
|
||||
if installed.is_relative_to(checkout):
|
||||
raise RuntimeError('package smoke imported checkout instead of installed wheel')
|
||||
catalog = json.loads(files('hub_core.security').joinpath('routes.json').read_text())['routes']
|
||||
app = create_app(settings=RuntimeSettings(environment='test', access_mode='enforce'))
|
||||
routes = [*iter_routes(app), *iter_routes(create_inbox_projection_router())]
|
||||
missing = [route_key(route, method) for route in routes if hasattr(route, 'methods')
|
||||
for method in route.methods if route.path != '/healthz' and route_key(route, method) not in catalog]
|
||||
if missing:
|
||||
raise RuntimeError('installed action catalog is incomplete: ' + ', '.join(missing))
|
||||
browser = {(method, route.path) for route in iter_routes(create_browser_router())
|
||||
for method in route.methods}
|
||||
if browser != BROWSER_ROUTES:
|
||||
raise RuntimeError('installed browser route coverage differs')
|
||||
# Constructor loads packaged contract fixtures; these checks load schemas.
|
||||
harness = ConformanceHarness(None)
|
||||
harness._schema_validate()
|
||||
harness._no_secrets()
|
||||
if not files('hub_core.migrations').joinpath('script.py.mako').is_file():
|
||||
raise RuntimeError('installed migration template missing')
|
||||
print('Installed wheel: imports, action catalog, browser routes, contract resources and migration template pass')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -325,6 +325,28 @@ T01–T04 remain `progress`; issuer registration/MFA, owner-facts composition,
|
|||
MCP consumer adoption, operation-outcome auditing and platform conformance remain
|
||||
open. No production listener or entitlement changed.
|
||||
|
||||
## Conformance and CI continuation — 2026-09-28
|
||||
|
||||
Added enforcement-mode Tier 2/3 conformance journeys with signed IAM tokens and
|
||||
explicit synthetic owners. Denial/revocation/outage cases exercise reads and
|
||||
writes and verify unchanged business state after restoring access. Event
|
||||
attribution is joined to recorded authorization decisions.
|
||||
|
||||
Forgejo now runs `make ci-check`: the complete ordinary test suite, access
|
||||
inventory drift checks, distribution builds and an isolated installed-wheel
|
||||
resource/import check. CI checks out the full commit into a unique temporary
|
||||
directory and installs locked development/runtime dependencies. The optional
|
||||
owner-source interoperability suite remains separately identified.
|
||||
|
||||
See [conformance documentation](../docs/conformance.md#access-enforcement-and-ci-gates).
|
||||
Validation: local `make ci-check` passed with **330 tests**, one explicitly
|
||||
optional owner-source module skipped, inventory coverage (165 Hub surfaces),
|
||||
distribution builds and installed-wheel checks. Final locked runtime dependency
|
||||
sync and workflow YAML/shell syntax checks also pass.
|
||||
|
||||
These are local source/release gates, not live platform or remote CI acceptance;
|
||||
T01–T04 remain `progress` and T06 remains open.
|
||||
|
||||
## Acceptance checkpoints
|
||||
|
||||
- [x] Architecture/source/runtime review captured; new implementation owner is hub-core
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue