6 Commits
Author SHA1 Message Date
Gitea Actions f33a956494 Release 0.1.5 [auto-version-bump] 2026-10-20 11:42:34 +02:00
Ein Anderssono c4403f297a Restrict worker egress and publish manifest
Build and publish rule engine / release (push) Successful in 2m48s
2026-10-20 11:42:13 +02:00
Gitea Actions 6f67b8012f Release 0.1.4 [auto-version-bump] 2026-10-20 11:38:55 +02:00
Ein Anderssono 300bc3c61c Version the rule schema with releases
Build and publish rule engine / release (push) Failing after 2m32s
2026-10-20 11:38:41 +02:00
Gitea Actions 7e92eb651c Release 0.1.3 [auto-version-bump] 2026-10-20 11:35:28 +02:00
Ein Anderssono b9f832858a Scope rule engine rollout permissions
Build and publish rule engine / release (push) Failing after 11s
2026-10-20 11:35:18 +02:00
10 changed files with 133 additions and 15 deletions
+18 -2
View File
@@ -19,6 +19,16 @@ jobs:
token: ${{ secrets.GITEA_TOKEN }}
persist-credentials: true
- name: Check release credentials before changing the version
shell: bash
env:
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
run: |
set -euo pipefail
test -n "$HARBOR_USERNAME" || { echo 'Missing HARBOR_USERNAME action secret'; exit 1; }
test -n "$HARBOR_PASSWORD" || { echo 'Missing HARBOR_PASSWORD action secret'; exit 1; }
- name: Test and lint in the shared builder image
shell: bash
run: |
@@ -90,9 +100,12 @@ jobs:
install "$RUNNER_TEMP/oras/oras" "$RUNNER_TEMP/oras-bin"
printf '%s' "$HARBOR_PASSWORD" | "$RUNNER_TEMP/oras-bin" login harbor.k3s.k0.nu --username "$HARBOR_USERNAME" --password-stdin
unset HARBOR_PASSWORD
(
cd "$package_dir"
"$RUNNER_TEMP/oras-bin" push --artifact-type application/vnd.ai-spel.kubernetes-manifest.v1 \
"harbor.k3s.k0.nu/ai-spel/rule-engine-manifest:$version" \
"$package_dir/manifest.yaml:application/yaml"
"manifest.yaml:application/yaml"
)
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Deploy rule engine through Argo CD
@@ -115,8 +128,11 @@ jobs:
echo 'NATS is not installed yet. The backend release pipeline bootstraps NATS and this Argo Application.'
exit 0
fi
if ! kubectl -n argocd get application ai-spel-rule-engine >/dev/null 2>&1; then
echo 'The backend release pipeline owns first-time Argo Application creation; skipping this worker-only rollout.'
exit 0
fi
kubectl -n ai-spel rollout status statefulset/ai-spel-nats --timeout=10m
kubectl -n argocd apply -f deploy/argocd/application.yaml
kubectl -n argocd patch application ai-spel-rule-engine --type merge -p "{\"spec\":{\"source\":{\"targetRevision\":\"$version\"}}}"
deadline=$((SECONDS + 600))
while (( SECONDS < deadline )); do
Generated
+1 -1
View File
@@ -13,7 +13,7 @@ dependencies = [
[[package]]
name = "ai-spel-rule-engine"
version = "0.1.1"
version = "0.1.5"
dependencies = [
"async-nats",
"axum",
+1 -1
View File
@@ -3,7 +3,7 @@ members = ["crates/rule-engine"]
resolver = "2"
[workspace.package]
version = "0.1.2"
version = "0.1.5"
edition = "2024"
license = "MIT"
+15 -2
View File
@@ -8,12 +8,15 @@ remains responsible for validating and committing effects.
## Contract
Requests use NATS subject `ai-spel.rules.evaluate.v1` and contract version 1.
The canonical JSON Schema is `contracts/rule-evaluation-v1.schema.json`.
The canonical JSON Schema is `contracts/rule-evaluation-v1.schema.json`; its
versioned `$id` is updated by the release version-bump workflow.
Responses echo the correlation ID and have status `evaluated` or `rejected`.
The NATS user may subscribe only to the evaluation subject and publish only to
reply inboxes. The service receives no credentials other than its own NATS
identity and has no configured database or AI-provider secrets.
identity and has no configured database or AI-provider secrets. Its chart
limits pod egress to NATS and cluster DNS; Luau evaluation has no external
network path.
## Local commands
@@ -21,3 +24,13 @@ For source development, the Rust commands are `cargo fmt --all -- --check`,
`cargo test --workspace`, and `cargo clippy --workspace --all-targets -- -D warnings`.
Production builds, tests, image builds, and executable verification for this
project are run by Gitea Actions on the existing `large` runner and Kubernetes.
## Release deployment boundary
The API repository owns first-time NATS and Argo Application provisioning. The
worker release pipeline publishes this repository's versioned image and chart,
then updates the existing `ai-spel-rule-engine` Argo Application. It uses the
`AI_SPEL_TEST_KUBECONFIG` repository action secret, scoped to patch that one
Argo Application and read rollout health in namespace `ai-spel`. The one-time
service-account roles are described in `deploy/ci/namespace-scoped-rbac.yaml`;
they do not grant database, secret, workload-write, or cluster-wide access.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://gitea.k3s.k0.nu/ai-spel/ai-spel-rule-engine/raw/tag/v0.1.0/contracts/rule-evaluation-v1.schema.json",
"$id": "https://gitea.k3s.k0.nu/ai-spel/ai-spel-rule-engine/raw/tag/v0.1.5/contracts/rule-evaluation-v1.schema.json",
"title": "AI Spel Luau rule evaluation contract v1",
"type": "object",
"additionalProperties": false,
+1 -1
View File
@@ -8,7 +8,7 @@ spec:
source:
repoURL: harbor.k3s.k0.nu/ai-spel
chart: ai-spel-rule-engine
targetRevision: "0.1.2"
targetRevision: "0.1.5"
helm:
releaseName: ai-spel-rule-engine
destination:
+66
View File
@@ -0,0 +1,66 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: ai-spel-rule-engine-deployer
namespace: argocd
---
apiVersion: v1
kind: Secret
metadata:
name: ai-spel-rule-engine-deployer-token
namespace: argocd
annotations:
kubernetes.io/service-account.name: ai-spel-rule-engine-deployer
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ai-spel-rule-engine-deployer
namespace: argocd
rules:
- apiGroups: [argoproj.io]
resources: [applications]
resourceNames: [ai-spel-rule-engine]
verbs: [get, patch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ai-spel-rule-engine-deployer
namespace: argocd
subjects:
- kind: ServiceAccount
name: ai-spel-rule-engine-deployer
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ai-spel-rule-engine-deployer
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ai-spel-rule-engine-deployer
namespace: ai-spel
rules:
- apiGroups: [apps]
resources: [deployments, replicasets, statefulsets]
verbs: [get, list, watch]
- apiGroups: [""]
resources: [pods, events]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ai-spel-rule-engine-deployer
namespace: ai-spel
subjects:
- kind: ServiceAccount
name: ai-spel-rule-engine-deployer
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ai-spel-rule-engine-deployer
+2 -2
View File
@@ -2,5 +2,5 @@ apiVersion: v2
name: ai-spel-rule-engine
description: Isolated, sandboxed Luau proposal evaluator
type: application
version: 0.1.2
appVersion: "0.1.2"
version: 0.1.5
appVersion: "0.1.5"
@@ -12,6 +12,7 @@ spec:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: ai-spel-nats
app.kubernetes.io/instance: ai-spel-nats
ports:
- protocol: TCP
@@ -20,9 +21,6 @@ spec:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
+25
View File
@@ -9,9 +9,22 @@ match = re.search(r'(?ms)(^\[workspace\.package\]\s+.*?^version\s*=\s*")(\d+)\.(
if not match:
raise SystemExit("Missing [workspace.package].version")
major, minor, patch = map(int, match.group(2, 3, 4))
current_version = f"{major}.{minor}.{patch}"
version = f"{major}.{minor}.{patch + 1}"
cargo_path.write_text(cargo[:match.start(2)] + version + cargo[match.end(4):])
lock_path = Path("Cargo.lock")
lock = lock_path.read_text()
lock_pattern = rf'(?ms)^\[\[package\]\]\nname\s*=\s*"ai-spel-rule-engine"\nversion\s*=\s*"{re.escape(current_version)}"'
if not re.search(lock_pattern, lock):
raise SystemExit(f"Missing ai-spel-rule-engine {current_version} package in {lock_path}")
lock_path.write_text(re.sub(
lock_pattern,
f'[[package]]\nname = "ai-spel-rule-engine"\nversion = "{version}"',
lock,
count=1,
))
for path, pattern in (
(Path("deploy/helm/ai-spel-rule-engine/Chart.yaml"), r'(?m)^version:\s*\d+\.\d+\.\d+\s*$'),
(Path("deploy/helm/ai-spel-rule-engine/Chart.yaml"), r'(?m)^appVersion:\s*"\d+\.\d+\.\d+"\s*$'),
@@ -24,4 +37,16 @@ for path, pattern in (
f'appVersion: "{version}"' if pattern.startswith(r'(?m)^appVersion') else f' targetRevision: "{version}"'
)
path.write_text(re.sub(pattern, replacement, contents, count=1))
schema_path = Path("contracts/rule-evaluation-v1.schema.json")
schema = schema_path.read_text()
schema_pattern = r'(?m)^ "\$id": "https://gitea\.k3s\.k0\.nu/ai-spel/ai-spel-rule-engine/raw/tag/v\d+\.\d+\.\d+/contracts/rule-evaluation-v1\.schema\.json",$'
if not re.search(schema_pattern, schema):
raise SystemExit(f"Missing versioned schema id in {schema_path}")
schema_path.write_text(re.sub(
schema_pattern,
f' "$id": "https://gitea.k3s.k0.nu/ai-spel/ai-spel-rule-engine/raw/tag/v{version}/contracts/rule-evaluation-v1.schema.json",',
schema,
count=1,
))
print(version)