mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 19:59:26 +00:00
merge(plane-on-kubernetes): groups 1-2 -- cluster profile, no host literals, delivery through the S3 store
Kind proofs: records 01 and 02. Task 2.6 (receiver removal) is paused while the safety net and the decision engine come first. deployment.md conflicts resolved in the plain-language style; corrects the RuntimeClass claim (Substrate runs runsc inside its worker pod). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"modules": 155,
|
||||
"modules": 157,
|
||||
"counts": {
|
||||
"load_bearing": 150,
|
||||
"load_bearing": 152,
|
||||
"standalone_capability": 5
|
||||
},
|
||||
"orphaned": [],
|
||||
@@ -855,6 +855,8 @@
|
||||
"learn.examples",
|
||||
"learn.experiments",
|
||||
"learn.program",
|
||||
"plane.delivery",
|
||||
"plane.doctor",
|
||||
"plane.egress",
|
||||
"plane.engine_alerts",
|
||||
"plane.generate",
|
||||
@@ -885,6 +887,14 @@
|
||||
"plane.tasks.verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "plane.delivery",
|
||||
"classification": "load_bearing",
|
||||
"reason": "reachable from gate, map_gate, pair_gate, cli",
|
||||
"importers": [
|
||||
"plane.reconciler"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "plane.doctor",
|
||||
"classification": "load_bearing",
|
||||
@@ -932,6 +942,7 @@
|
||||
"reason": "ai-service-plane Req 1.1-1.4: ax-shaped Task/Workspace/Model and the Run kind, validated before anything runs",
|
||||
"importers": [
|
||||
"learn.cli",
|
||||
"plane.delivery",
|
||||
"plane.egress",
|
||||
"plane.generate",
|
||||
"plane.harvest",
|
||||
@@ -954,11 +965,25 @@
|
||||
"learn.examples",
|
||||
"learn.experiments",
|
||||
"learn.program",
|
||||
"plane.delivery",
|
||||
"plane.doctor",
|
||||
"plane.generate",
|
||||
"plane.reconciler",
|
||||
"plane.tasks.loop",
|
||||
"plane.tasks.remember"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "plane.profile",
|
||||
"classification": "load_bearing",
|
||||
"reason": "python -m openultrasast.plane.profile",
|
||||
"importers": [
|
||||
"cli",
|
||||
"plane.doctor",
|
||||
"plane.generate",
|
||||
"plane.reconciler"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "plane.reconciler",
|
||||
"classification": "load_bearing",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[ok] profile k3s: exec=remote kube_context=ousast-k3s registry=ghcr.io/norandom images=/home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/images.json router_url=(tunnel through the kube context) ax_server=(tunnel through the kube context) memory=s3://sast-memory atespace=default snapshots_bucket=s3://ax-snapshots image_pull_secret=ghcr-pull
|
||||
[FAIL] cluster ousast-k3s: context ousast-k3s unreachable (error: context "ousast-k3s" does not exist); docs/deployment.md lists the prerequisites
|
||||
[FAIL] runner image in ghcr.io/norandom: images file /home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/images.json: No such file or directory; docs/deployment.md lists the prerequisites
|
||||
[ok] memory store s3://sast-memory: s3://sast-memory opens (an S3 bucket is verified)
|
||||
exit=1 seconds=1.1
|
||||
@@ -0,0 +1,7 @@
|
||||
[ok] profile kind: exec=local kube_context=kind-ousast registry=localhost:5001 images=/home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/kind-images.json router_url=(tunnel through the kube context) ax_server=(tunnel through the kube context) memory=s3://sast-memory atespace=default snapshots_bucket=s3://ax-snapshots image_pull_secret=(none)
|
||||
[ok] cluster kind-ousast: reachable
|
||||
[ok] agent substrate (ate-system): 9 pods ready
|
||||
[ok] ax controller (ax-system): 4 pods ready
|
||||
[ok] runner image in localhost:5001: ousast-runner@sha256:95edc7ed5621... present in localhost:5001
|
||||
[ok] memory store s3://sast-memory: s3://sast-memory opens (an S3 bucket is verified)
|
||||
exit=0 seconds=1.4
|
||||
@@ -0,0 +1,19 @@
|
||||
F.. [100%]
|
||||
=================================== FAILURES ===================================
|
||||
________ test_no_host_literal_outside_the_profile_and_the_kind_bring_up ________
|
||||
|
||||
def test_no_host_literal_outside_the_profile_and_the_kind_bring_up() -> None:
|
||||
kind = load_profile("kind", environ={})
|
||||
found = offenders(_files(), kind.registry)
|
||||
> assert found == [], "host-only literals (Req 1.3):\n" + "\n".join(found)
|
||||
E AssertionError: host-only literals (Req 1.3):
|
||||
E src/openultrasast/plane/workspaces.py:37: [kind context] CONTEXT = "kind-ousast" # reinserted for the record 01 demonstration
|
||||
E assert ['src/openult...emonstration'] == []
|
||||
E
|
||||
E Left contains one more item: 'src/openultrasast/plane/workspaces.py:37: [kind context] CONTEXT = "kind-ousast" # reinserted for the record 01 demonstration'
|
||||
E Use -v to get more diff
|
||||
|
||||
tests/test_plane_literals.py:81: AssertionError
|
||||
=========================== short test summary info ============================
|
||||
FAILED tests/test_plane_literals.py::test_no_host_literal_outside_the_profile_and_the_kind_bring_up
|
||||
1 failed, 2 passed in 0.26s
|
||||
@@ -0,0 +1,2 @@
|
||||
... [100%]
|
||||
3 passed in 0.23s
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: ax.io/v1alpha1
|
||||
kind: Workspace
|
||||
metadata: {name: ousast-probe-big-ws, atespace: default}
|
||||
spec:
|
||||
files:
|
||||
- {path: "hello.py", content: "def hello():\n return greet()\n"}
|
||||
---
|
||||
apiVersion: ax.io/v1alpha1
|
||||
kind: Task
|
||||
metadata: {name: ousast-probe-big, atespace: default}
|
||||
spec:
|
||||
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
|
||||
command: ["repo-facts"]
|
||||
env: [{name: OUSAST_OUTPUT_DIR, value: "/workspace/.ousast-out/probe"}]
|
||||
resources: {requests: {cpu: "250m", memory: "2560Mi"}, limits: {cpu: "1", memory: "3Gi"}}
|
||||
workspaces: [{name: ousast-probe-big-ws, path: "/workspace"}]
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: ax.io/v1alpha1
|
||||
kind: Workspace
|
||||
metadata: {name: ousast-probe-small-ws, atespace: default}
|
||||
spec:
|
||||
files:
|
||||
- {path: "hello.py", content: "def hello():\n return greet()\n"}
|
||||
---
|
||||
apiVersion: ax.io/v1alpha1
|
||||
kind: Task
|
||||
metadata: {name: ousast-probe-small, atespace: default}
|
||||
spec:
|
||||
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
|
||||
command: ["repo-facts"]
|
||||
env: [{name: OUSAST_OUTPUT_DIR, value: "/workspace/.ousast-out/probe"}]
|
||||
resources: {requests: {cpu: "250m", memory: "256Mi"}, limits: {cpu: "1", memory: "512Mi"}}
|
||||
workspaces: [{name: ousast-probe-small-ws, path: "/workspace"}]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Warm gVisor workers for ax tasks (Agent Substrate WorkerPool), rendered by ops/ax/up.sh (and by
|
||||
# ops/ax/probe-placement.sh for the placement probe) from the profile's pool: POOL_NAME, POOL_REPLICAS, POOL_CPU,
|
||||
# POOL_MEMORY (ops/k8s/profiles/<profile>.toml, [pools.default]) and WORKER_IMAGE (ko:// for a build, or a
|
||||
# digest-pinned reference of a built worker image). ax's per-task ActorTemplates set no workerSelector, so every
|
||||
# pool of the matching sandbox class is eligible. An actor occupies a whole worker. Replaced by the renderer of
|
||||
# plane-on-kubernetes task 6.1 (ops/k8s/base/workerpool-*.yaml.tmpl).
|
||||
apiVersion: ate.dev/v1alpha1
|
||||
kind: WorkerPool
|
||||
metadata:
|
||||
name: probe-pool
|
||||
namespace: ax-system
|
||||
labels:
|
||||
workload: ousast
|
||||
spec:
|
||||
replicas: 1
|
||||
workerImage: localhost:5001/ateom-gvisor-715889664656de67e44382a8d6ab981d@sha256:220f1cf13ea485b73a2e4270fa544901a18ae826d8f22bd54d42761944a41c5c
|
||||
template:
|
||||
nodeSelector:
|
||||
ate.dev/substrate-version: "4f9c293"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 3Gi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 3Gi
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"workers": [
|
||||
{
|
||||
"ip": "10.244.0.22",
|
||||
"labels": {
|
||||
"workload": "ousast"
|
||||
},
|
||||
"metadata": {
|
||||
"createTime": "2026-09-29T16:32:08.594663703Z",
|
||||
"name": "769f7d8b-142a-4ed5-b896-08d6a04bedb6",
|
||||
"uid": "152cf7c0-3f4d-4634-88c5-9fe501bb403e",
|
||||
"updateTime": "2026-10-02T05:24:59.069304031Z",
|
||||
"version": "1758"
|
||||
},
|
||||
"nodeName": "ousast-control-plane",
|
||||
"sandboxClass": "gvisor",
|
||||
"status": {
|
||||
"capacity": {
|
||||
"actors": 1000,
|
||||
"resources": {
|
||||
"limits": [
|
||||
{
|
||||
"name": "cpu",
|
||||
"quantity": "1"
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"quantity": "1536Mi"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"state": "WORKER_STATE_ACTIVE"
|
||||
},
|
||||
"workerNamespace": "ax-system",
|
||||
"workerPod": "ousast-pool-6656b55f5b-9wbrw",
|
||||
"workerPodUid": "769f7d8b-142a-4ed5-b896-08d6a04bedb6",
|
||||
"workerPool": "ousast-pool"
|
||||
},
|
||||
{
|
||||
"ip": "10.244.0.28",
|
||||
"labels": {
|
||||
"workload": "ousast"
|
||||
},
|
||||
"metadata": {
|
||||
"createTime": "2026-10-02T18:29:30.486026311Z",
|
||||
"name": "1407a421-7dcf-4ad7-b999-70a4624bf890",
|
||||
"uid": "2d496d7a-4f32-459d-9b92-256a2c5b4800",
|
||||
"updateTime": "2026-10-02T18:29:30.551668391Z",
|
||||
"version": "2"
|
||||
},
|
||||
"nodeName": "ousast-control-plane",
|
||||
"sandboxClass": "gvisor",
|
||||
"status": {
|
||||
"capacity": {
|
||||
"actors": 1000,
|
||||
"resources": {
|
||||
"limits": [
|
||||
{
|
||||
"name": "cpu",
|
||||
"quantity": "2"
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"quantity": "3Gi"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"state": "WORKER_STATE_ACTIVE"
|
||||
},
|
||||
"workerNamespace": "ax-system",
|
||||
"workerPod": "probe-pool-5dffd74bdc-dqlww",
|
||||
"workerPodUid": "1407a421-7dcf-4ad7-b999-70a4624bf890",
|
||||
"workerPool": "probe-pool"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"workers": [
|
||||
{
|
||||
"ip": "10.244.0.22",
|
||||
"labels": {
|
||||
"workload": "ousast"
|
||||
},
|
||||
"metadata": {
|
||||
"createTime": "2026-09-29T16:32:08.594663703Z",
|
||||
"name": "769f7d8b-142a-4ed5-b896-08d6a04bedb6",
|
||||
"uid": "152cf7c0-3f4d-4634-88c5-9fe501bb403e",
|
||||
"updateTime": "2026-10-02T18:29:45.281958347Z",
|
||||
"version": "1761"
|
||||
},
|
||||
"nodeName": "ousast-control-plane",
|
||||
"sandboxClass": "gvisor",
|
||||
"status": {
|
||||
"allocated": {
|
||||
"actors": 1
|
||||
},
|
||||
"capacity": {
|
||||
"actors": 1000,
|
||||
"resources": {
|
||||
"limits": [
|
||||
{
|
||||
"name": "cpu",
|
||||
"quantity": "1"
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"quantity": "1536Mi"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"state": "WORKER_STATE_ACTIVE"
|
||||
},
|
||||
"workerNamespace": "ax-system",
|
||||
"workerPod": "ousast-pool-6656b55f5b-9wbrw",
|
||||
"workerPodUid": "769f7d8b-142a-4ed5-b896-08d6a04bedb6",
|
||||
"workerPool": "ousast-pool"
|
||||
},
|
||||
{
|
||||
"ip": "10.244.0.28",
|
||||
"labels": {
|
||||
"workload": "ousast"
|
||||
},
|
||||
"metadata": {
|
||||
"createTime": "2026-10-02T18:29:30.486026311Z",
|
||||
"name": "1407a421-7dcf-4ad7-b999-70a4624bf890",
|
||||
"uid": "2d496d7a-4f32-459d-9b92-256a2c5b4800",
|
||||
"updateTime": "2026-10-02T18:29:30.551668391Z",
|
||||
"version": "2"
|
||||
},
|
||||
"nodeName": "ousast-control-plane",
|
||||
"sandboxClass": "gvisor",
|
||||
"status": {
|
||||
"capacity": {
|
||||
"actors": 1000,
|
||||
"resources": {
|
||||
"limits": [
|
||||
{
|
||||
"name": "cpu",
|
||||
"quantity": "2"
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"quantity": "3Gi"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"state": "WORKER_STATE_ACTIVE"
|
||||
},
|
||||
"workerNamespace": "ax-system",
|
||||
"workerPod": "probe-pool-5dffd74bdc-dqlww",
|
||||
"workerPodUid": "1407a421-7dcf-4ad7-b999-70a4624bf890",
|
||||
"workerPool": "probe-pool"
|
||||
}
|
||||
]
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"workers": [
|
||||
{
|
||||
"ip": "10.244.0.22",
|
||||
"labels": {
|
||||
"workload": "ousast"
|
||||
},
|
||||
"metadata": {
|
||||
"createTime": "2026-09-29T16:32:08.594663703Z",
|
||||
"name": "769f7d8b-142a-4ed5-b896-08d6a04bedb6",
|
||||
"uid": "152cf7c0-3f4d-4634-88c5-9fe501bb403e",
|
||||
"updateTime": "2026-10-02T18:29:53.001457323Z",
|
||||
"version": "1763"
|
||||
},
|
||||
"nodeName": "ousast-control-plane",
|
||||
"sandboxClass": "gvisor",
|
||||
"status": {
|
||||
"allocated": {
|
||||
"actors": 1
|
||||
},
|
||||
"capacity": {
|
||||
"actors": 1000,
|
||||
"resources": {
|
||||
"limits": [
|
||||
{
|
||||
"name": "cpu",
|
||||
"quantity": "1"
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"quantity": "1536Mi"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"state": "WORKER_STATE_ACTIVE"
|
||||
},
|
||||
"workerNamespace": "ax-system",
|
||||
"workerPod": "ousast-pool-6656b55f5b-9wbrw",
|
||||
"workerPodUid": "769f7d8b-142a-4ed5-b896-08d6a04bedb6",
|
||||
"workerPool": "ousast-pool"
|
||||
},
|
||||
{
|
||||
"ip": "10.244.0.28",
|
||||
"labels": {
|
||||
"workload": "ousast"
|
||||
},
|
||||
"metadata": {
|
||||
"createTime": "2026-10-02T18:29:30.486026311Z",
|
||||
"name": "1407a421-7dcf-4ad7-b999-70a4624bf890",
|
||||
"uid": "2d496d7a-4f32-459d-9b92-256a2c5b4800",
|
||||
"updateTime": "2026-10-02T18:30:04.825058843Z",
|
||||
"version": "4"
|
||||
},
|
||||
"nodeName": "ousast-control-plane",
|
||||
"sandboxClass": "gvisor",
|
||||
"status": {
|
||||
"capacity": {
|
||||
"actors": 1000,
|
||||
"resources": {
|
||||
"limits": [
|
||||
{
|
||||
"name": "cpu",
|
||||
"quantity": "2"
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"quantity": "3Gi"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"state": "WORKER_STATE_ACTIVE"
|
||||
},
|
||||
"workerNamespace": "ax-system",
|
||||
"workerPod": "probe-pool-5dffd74bdc-dqlww",
|
||||
"workerPodUid": "1407a421-7dcf-4ad7-b999-70a4624bf890",
|
||||
"workerPool": "probe-pool"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"record": "k8s-01-profile",
|
||||
"spec": "plane-on-kubernetes",
|
||||
"tasks": ["1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8"],
|
||||
"date": "2026-10-02",
|
||||
"host": "the maintainer's laptop: 7.9 GiB, single-node kind cluster, RustFS store reachable over https",
|
||||
"commits": {
|
||||
"1.1": "8eac1b2",
|
||||
"1.2": "77a03ba",
|
||||
"1.3": "36f5927",
|
||||
"1.4": "8099b29",
|
||||
"1.5": "86ec237",
|
||||
"1.6": "2afa20e",
|
||||
"1.7": "2cca03c",
|
||||
"1.8": "the commit that adds this line (git log: plane(1.8))"
|
||||
},
|
||||
"images": {
|
||||
"runner_before": "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884",
|
||||
"runner_after_up_sh": "localhost:5001/ousast-runner@sha256:95edc7ed562189180a472df8115e6a90c8b4fcea47c06593395634bd56041bcb",
|
||||
"pin_file": "ops/k8s/profiles/kind-images.json (written by ops/ax/up.sh; the templates re-pinned to it, 128 image lines)"
|
||||
},
|
||||
"instrument_checks": {
|
||||
"code_under_test": "PYTHONPATH=<worktree>/src first; `python -c 'import openultrasast; print(openultrasast.__file__)'` printed the worktree path before every gate",
|
||||
"store_reached": {
|
||||
"how": "scratch store_probe.py: open_store('s3://sast-memory') through the worktree code (verify_bucket runs), then read the probe object",
|
||||
"open_and_verify_seconds": 0.62,
|
||||
"probe_key": "_probe/select.jsonl",
|
||||
"probe_bytes": 24,
|
||||
"probe_version_id_present": true
|
||||
},
|
||||
"smoke_delivery_sizes_bytes": {"facts.json": 451, "summary.json": 238, "units.jsonl": 291, "task.yaml": 1148},
|
||||
"registry_resolution": "doctor HEADs /v2/ousast-runner/manifests/<digest> on the kind registry: HTTP 2xx for the new digest"
|
||||
},
|
||||
"doctor": {
|
||||
"kind": {
|
||||
"command": "ousast plane doctor --profile kind",
|
||||
"exit": 0,
|
||||
"seconds": 1.4,
|
||||
"lines": [
|
||||
"[ok] profile kind: exec=local kube_context=... registry=... images=... router_url=(tunnel) ax_server=(tunnel) memory=s3://sast-memory atespace=default snapshots_bucket=s3://ax-snapshots image_pull_secret=(none)",
|
||||
"[ok] cluster <kind context>: reachable",
|
||||
"[ok] agent substrate (ate-system): 9 pods ready",
|
||||
"[ok] ax controller (ax-system): 4 pods ready",
|
||||
"[ok] runner image in <kind registry>: ousast-runner@sha256:95edc7ed5621... present",
|
||||
"[ok] memory store s3://sast-memory: opens (an S3 bucket is verified)"
|
||||
],
|
||||
"file": "doctor-kind.txt"
|
||||
},
|
||||
"k3s": {
|
||||
"command": "ousast plane doctor --profile k3s",
|
||||
"exit": 1,
|
||||
"seconds": 1.1,
|
||||
"failing_lines": [
|
||||
"cluster ousast-k3s: context does not exist (expected: the cluster does not exist yet)",
|
||||
"runner image in ghcr.io/norandom: images file ops/k8s/profiles/images.json missing (expected: the release asset is published by task 4.1)"
|
||||
],
|
||||
"passing_lines": ["profile k3s: every address reported", "memory store s3://sast-memory: opens"],
|
||||
"note": "two expected failures, not one: the images file is the release asset and does not exist before group 4; the namespaces are not checked behind an unreachable context, so no third line fails for the same cause",
|
||||
"file": "doctor-k3s.txt"
|
||||
}
|
||||
},
|
||||
"literal_scan": {
|
||||
"test": "tests/test_plane_literals.py",
|
||||
"scope": "src/, plane/, ops/ (minus ops/ax/up.sh, ops/k8s/profiles/), docs/",
|
||||
"patterns": ["kind context", "kind registry", "kind gateway 172.19.", "loopback with port", "port-forward"],
|
||||
"exemptions": [
|
||||
"router.py for loopback-with-port and port-forward",
|
||||
"lines tagged ax-tunnel or loopback (two: the runner's own health URL, the Joern session's query URL)",
|
||||
"digest-pinned image: lines under plane/tasks/ whose registry equals the kind profile's (a third test asserts every one does)"
|
||||
],
|
||||
"offenders_at_1_3": 0,
|
||||
"offenders_before_1_3": 23,
|
||||
"reinsertion_demo": {
|
||||
"inserted": "a kind-context literal appended to src/openultrasast/plane/workspaces.py",
|
||||
"pytest_exit_with_literal": 1,
|
||||
"named": "src/openultrasast/plane/workspaces.py:37: [kind context]",
|
||||
"restored": "byte for byte (cmp)",
|
||||
"pytest_exit_restored": 0,
|
||||
"files": ["literal-reinserted-fail.txt", "literal-restored-pass.txt"]
|
||||
}
|
||||
},
|
||||
"up_sh_rerun": {
|
||||
"command": "OUSAST_PYTHON=<main venv python> ops/ax/up.sh",
|
||||
"exit": 0,
|
||||
"seconds": 38,
|
||||
"kept": ["cluster", "substrate", "egress gateway", "ax control plane", "worker pool ousast-pool (2/2 ready)", "ax CLI (newer than the checkout)"],
|
||||
"done": ["receiver Service re-applied", "runner image rebuilt and pushed (new digest above)", "kind-images.json written", "smoke Task applied and resumed"],
|
||||
"first_attempt_failure": "`profile --print images` answered `no image named ''`; fixed in profile._lookup (an images path with no sub-key is the file path) and covered by test_plane_profile",
|
||||
"disk_gb_free_before_after": [1.7, 1.5],
|
||||
"file": "up-sh-rerun.txt"
|
||||
},
|
||||
"smoke_run_1_4": {
|
||||
"command": "ops/ax/smoke-run.sh (receiver delivery: the path group 2 replaces)",
|
||||
"exit": 0,
|
||||
"seconds": 24,
|
||||
"status": "done",
|
||||
"delivered": "facts.json 451 B, summary.json 238 B, units.jsonl 291 B; summary.image = the new digest, units 2/2",
|
||||
"file": "smoke-run-1.4.txt"
|
||||
},
|
||||
"placement_probe_1_5": {
|
||||
"script": "ops/ax/probe-placement.sh",
|
||||
"pools": {"ousast-pool": "scaled to 1 worker, 1 CPU / 1536Mi", "probe-pool": "1 worker, 2 CPU / 3Gi, the already-built ateom-gvisor image (no ko build)"},
|
||||
"big_actor": {"requests_memory": "2560Mi", "limits_memory": "3Gi", "landed_on": "ousast-pool (1536Mi worker): status.allocated.actors=1; probe-pool 0"},
|
||||
"small_actor": {"requests_memory": "256Mi", "limits_memory": "512Mi", "landed_on": "ousast-pool: allocated.actors=1; probe-pool 0"},
|
||||
"placement": "any",
|
||||
"consequence": "design.md section 4: group 5 takes the fallback (an engine atespace with engine-pool as its only pool if Substrate scopes pools by atespace; else a plain Kubernetes Job on k3s and alerts-engine on kind)",
|
||||
"resume_latency_seconds": "first resume of each new template succeeded at once (golden snapshot built within the resume deadline); whole probe 46 s",
|
||||
"cleanup": "probe tasks deleted, probe-pool deleted, ousast-pool back to 2/2 (verified by kubectl get workerpool)",
|
||||
"files": ["probe/probe.log", "probe/workers-before.json", "probe/workers-ousast-probe-big.json", "probe/workers-ousast-probe-small.json", "probe/ousast-probe-big.yaml", "probe/ousast-probe-small.yaml", "probe/probe-pool.yaml"]
|
||||
},
|
||||
"reconciler_bound_1_6": {
|
||||
"lines_at_spec_commit_6bd44ca": 477,
|
||||
"lines_after_1_2": 489,
|
||||
"added_by_1_2": "the profile parameter of run() and its docstring (+12)",
|
||||
"estimate_2_4": "Delivery wiring: open the store, build Delivery, pass the delivery body to the starter, delivered() and collect() in the await/execute path, state mirroring, the OUSAST_DELIVERY=receiver switch: about +25 while the receiver is kept (group 2 keeps both paths), about -20 when 2.6 removes the receiver wiring",
|
||||
"projected_during_group_2": 514,
|
||||
"projected_after_2_6": 494,
|
||||
"decision": "the estimate crosses 500 while both paths coexist: task 2.4 moves the bound in tests/test_plane_reconciler.py to 550 with the reason in the assertion message, and 2.6 records the final count"
|
||||
},
|
||||
"gvisor_tarball_1_7": {
|
||||
"status": "planned, manual",
|
||||
"where": "docs/deployment.md B.1",
|
||||
"url": "gs://gvisor/releases/nightly/2026-09-02/<arch>/gvisor.tar.zstd (sha256-pinned in substrate/manifests/ate-install/sandboxconfig-gvisor.yaml)",
|
||||
"check": "the atelet's fetch line in `kubectl -n ate-system logs` of a worker pod on the k3s node; labelled planned until read there"
|
||||
},
|
||||
"gates": {
|
||||
"1.1": "pytest 2002 passed + module-audit manifest regenerated (test_module_audit 6 passed), ruff 0, format 0, mypy 0",
|
||||
"1.2": "pytest 2006 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0",
|
||||
"1.3": "pytest 2009 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0; mkdocs --strict 0",
|
||||
"1.4": "see the commit message"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
run finished: done
|
||||
memory s3://sast-memory: ax-e2e-smoke: 0 tasks ingested, 0 already in the index; rows none
|
||||
run ax-e2e-smoke: done (/tmp/claude-1000/-home-mc-Source-OpenUltraSAST/c09e30b2-39a4-4564-bc31-6ea71ffaa198/scratchpad/k8s-impl/results/plane/ax-e2e-smoke)
|
||||
|
||||
TASK MODEL CALLS PROMPT CACHE-HIT OUTPUT USD
|
||||
facts - 0 n/a n/a n/a n/a [done]
|
||||
----------------------------------------------------------------------------------------------------
|
||||
total - n/a n/a n/a n/a n/a
|
||||
smoke exit=0 seconds=24
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
== kind cluster 'ousast' (context kind-ousast) + registry localhost:5001 (20:35:35)
|
||||
cluster exists; kept
|
||||
|
||||
== agent substrate (ate-system) (20:35:35)
|
||||
substrate ready; kept
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
ate-api-server-647bb6f8b6-c9k5k 1/1 Running 2 (12h ago) 3d
|
||||
ate-api-server-647bb6f8b6-dznjn 1/1 Running 1 (12h ago) 3d
|
||||
ate-controller-c95586684-g7nvn 1/1 Running 0 3d2h
|
||||
atelet-4f9c293-9zpfg 1/1 Running 0 3d2h
|
||||
atenet-egress-d74b8f6bf-8zmxj 1/1 Running 0 3d
|
||||
atenet-router-54887bc8f9-rqs6g 2/2 Running 2 (12h ago) 3d2h
|
||||
postgres-0 2/2 Running 3 (3d2h ago) 3d2h
|
||||
rustfs-6c4c677cb6-cqvhc 1/1 Running 3 (3d2h ago) 3d2h
|
||||
rustfs-bucket-init-nvfr6 0/1 Completed 0 3d2h
|
||||
|
||||
== egress gateway (atenet-egress, agentgateway variant: one prebuilt image, nothing built) (20:35:35)
|
||||
gateway present; kept
|
||||
|
||||
== ax control plane (ax-system) (20:35:35)
|
||||
ax ready; kept
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
ax-redis-86df786c87-pz5tp 1/1 Running 0 3d2h
|
||||
ax-server-b479bdc76-hwkqb 1/1 Running 0 3d2h
|
||||
ousast-pool-6656b55f5b-5brkn 1/1 Running 0 5m23s
|
||||
ousast-pool-6656b55f5b-9wbrw 1/1 Running 0 3d2h
|
||||
|
||||
== artifact receiver Service (ousast-receiver.ax-system -> this host) (20:35:35)
|
||||
service/ousast-receiver unchanged
|
||||
endpointslice.discovery.k8s.io/ousast-receiver-host configured
|
||||
|
||||
== worker pool (gVisor): pools.default of the profile (20:35:36)
|
||||
pool ousast-pool exists (2/2 ready); kept
|
||||
pod/ousast-pool-6656b55f5b-5brkn condition met
|
||||
pod/ousast-pool-6656b55f5b-9wbrw condition met
|
||||
|
||||
== runner image localhost:5001/ousast-runner (20:35:37)
|
||||
sha256:f004a5ef8f71b1cb8a5880c509389c0569181af7b19170b574958cc3ef75cb1c
|
||||
localhost:5001/ousast-runner:dev
|
||||
runner image localhost:5001/ousast-runner@sha256:95edc7ed562189180a472df8115e6a90c8b4fcea47c06593395634bd56041bcb -> /home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/kind-images.json
|
||||
re-pin the templates with: ousast plane workspaces ... --runner-image /home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/kind-images.json (or ousast plane repin, task 4.3)
|
||||
|
||||
== ax CLI from the deployed checkout (a release CLI skews from the server) (20:36:05)
|
||||
ax CLI newer than the checkout; kept
|
||||
|
||||
== smoke task (20:36:05)
|
||||
task.ax.io/ousast-smoke created
|
||||
workspace.ax.io/ousast-smoke-ws unchanged
|
||||
task.ax.io/ousast-smoke resumed
|
||||
NAME ATESPACE PHASE ACTOR WORKER-IP AGE
|
||||
ousast-smoke default Running ousast-smoke 10.244.0.29 5s
|
||||
|
||||
== footprint (20:36:11)
|
||||
total used free shared buff/cache available
|
||||
Mem: 7877 4797 225 63 3464 3079
|
||||
kind-registry 53.77MiB / 7.693GiB 0.00%
|
||||
up.sh exit 0
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"metadata": {
|
||||
"atespace": "default",
|
||||
"createTime": "2026-10-02T19:39:19.428045847Z",
|
||||
"name": "default",
|
||||
"uid": "741e52b7-aaf1-4a91-b4bf-fc3b20ba4a36",
|
||||
"updateTime": "2026-10-02T19:39:19.428045847Z",
|
||||
"version": "1"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"tlsPassthrough": {
|
||||
"hostnames": [
|
||||
"files.because-security.com"
|
||||
],
|
||||
"ports": {
|
||||
"numbers": [
|
||||
443
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"record": "k8s-02-store-delivery",
|
||||
"spec": "plane-on-kubernetes",
|
||||
"tasks": ["2.1", "2.2", "2.3", "2.4", "2.5", "2.6"],
|
||||
"date": "2026-10-02",
|
||||
"commits": {
|
||||
"2.1": "35d8032",
|
||||
"2.2": "9506a18",
|
||||
"2.3": "c49eae4",
|
||||
"2.4": "605b7af",
|
||||
"2.5": "the commit that adds this file (git log: plane(2.5))",
|
||||
"receiver_removed": "pending: task 2.6 (paused 2026-10-03 by the maintainer's reprioritisation; the receiver path still exists behind OUSAST_DELIVERY=receiver)"
|
||||
},
|
||||
"images": {
|
||||
"runner": "localhost:5001/ousast-runner@sha256:d2a55293999cc34581ef1f02a1ffb0c2dd8fc0cbe001ac952df81600e868f019",
|
||||
"built_by": "ops/ax/up.sh rerun (file up-sh-rebuild.txt): every step kept but the runner build; the image carries the 2.2 runner",
|
||||
"disk_gb_free_after_build": 1.3
|
||||
},
|
||||
"instrument_checks": {
|
||||
"code_under_test": "the worktree's src first on PYTHONPATH; `ousast` run as `python -m openultrasast.cli` from the worktree (ops/ax/smoke-run.sh with OUSAST_PYTHON)",
|
||||
"store_read_back": {
|
||||
"how": "scratch store_tar_check.py opens the store through the worktree code (verify_bucket runs), lists runs/ax-e2e-smoke/, downloads output.tar, compares its members with the files the reconciler extracted (file store-check.json)",
|
||||
"tar_bytes": 10240,
|
||||
"tar_members_bytes": {"facts.json": 451, "summary.json": 238, "units.jsonl": 291},
|
||||
"extracted_files_bytes": {"facts.json": 451, "summary.json": 238, "units.jsonl": 291},
|
||||
"members_equal_extracted": true,
|
||||
"member_bytes_total": 980,
|
||||
"note": "a tar is 512-byte blocks: 10240 B of tar carry 980 B of files; done.json records the tar's byte size"
|
||||
},
|
||||
"no_receiver_in_the_task": "the rendered task.yaml is 996 B against 1148 B on the receiver path: no OUSAST_ARTIFACT_URL/DIAL in the Task's env; the start body carries the URLs"
|
||||
},
|
||||
"smoke_runs": {
|
||||
"store_1": {
|
||||
"command": "ops/ax/smoke-run.sh (store delivery, the default after 2.4; OUSAST_DELIVERY unset)",
|
||||
"exit": 0,
|
||||
"seconds_submit_to_done": 26,
|
||||
"tar_put_at": "2026-10-02T19:37:24+00:00 (the object's LastModified)",
|
||||
"collected_at": "2026-10-02T19:37:26+00:00 (done.json finished)",
|
||||
"put_to_collect_seconds": 2,
|
||||
"poll_interval_seconds": 3,
|
||||
"file": "smoke-run-store-1.txt"
|
||||
},
|
||||
"seeded_rerun": {
|
||||
"command": "the same, with the local results root deleted and the store's runs/ax-e2e-smoke/ intact",
|
||||
"exit": 0,
|
||||
"seconds": 2,
|
||||
"what_happened": "state.json seeded from the store: every task done, nothing submitted to ax (plane-on-kubernetes 2.1, design section 2: a rerun from another machine resumes from the store)",
|
||||
"file": "smoke-run-seeded-rerun.txt"
|
||||
},
|
||||
"store_2": {
|
||||
"command": "the same, after deleting runs/ax-e2e-smoke/ from the store (5 objects) so the task executes again, with the egress policy polled",
|
||||
"exit": 0,
|
||||
"seconds_submit_to_done": 24,
|
||||
"tar_put_at": "2026-10-02T19:39:38+00:00",
|
||||
"collected_at": "2026-10-02T19:39:40+00:00",
|
||||
"put_to_collect_seconds": 2,
|
||||
"file": "smoke-run-store-2.txt"
|
||||
}
|
||||
},
|
||||
"objects_delivered_to_the_store": {
|
||||
"runs/ax-e2e-smoke/facts/output.tar": 10240,
|
||||
"runs/ax-e2e-smoke/facts/facts.json": 451,
|
||||
"runs/ax-e2e-smoke/facts/summary.json": 238,
|
||||
"runs/ax-e2e-smoke/facts/done.json": 149,
|
||||
"runs/ax-e2e-smoke/state.json": 350,
|
||||
"note": "units.jsonl is extracted locally but not a declared output of the smoke Run, so it is not re-put"
|
||||
},
|
||||
"egress_policy_read_back": {
|
||||
"how": "kubectl ate get egress-policy ax-e2e-smoke-facts -a default -o json, polled while the actor lived (file egress-policy-read-back.json)",
|
||||
"rules": [{"tlsPassthrough": {"hostnames": ["files.because-security.com"], "ports": {"numbers": [443]}}}],
|
||||
"reading": "one TLS rule naming the store host, no http rule; the model-free smoke task names no Git host and no Model host",
|
||||
"also": "Egress.apply reads every policy back and raises on a mismatch, so each completed Run is itself the equality check"
|
||||
},
|
||||
"status_table": "ousast plane status ax-e2e-smoke printed the attribution table (facts: done, calls 0) in every run; the table is in the smoke-run-*.txt files",
|
||||
"reconciler_lines": {"after_2_4": 549, "bound": 550, "note": "both paths coexist until 2.6; the receiver wiring goes then"},
|
||||
"gates": {
|
||||
"2.1": "pytest 2020 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0",
|
||||
"2.2": "pytest 2024 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0",
|
||||
"2.3": "pytest 2027 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0",
|
||||
"2.4": "pytest 2031 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0",
|
||||
"2.5": "pytest 2031 passed, 42 skipped, exit 0; ruff 0; format 0; mypy 0 (first attempt 2026-10-02: 11 failed + 5 errors, all the FileStore 1 GiB free-space refusal at 0.97 GiB free; rerun after the disk was freed)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
run finished: done
|
||||
memory s3://sast-memory: ax-e2e-smoke: 0 tasks ingested, 0 already in the index; rows none
|
||||
run ax-e2e-smoke: done (/tmp/claude-1000/-home-mc-Source-OpenUltraSAST/c09e30b2-39a4-4564-bc31-6ea71ffaa198/scratchpad/k8s-impl/results/plane/ax-e2e-smoke)
|
||||
|
||||
TASK MODEL CALLS PROMPT CACHE-HIT OUTPUT USD
|
||||
facts - n/a n/a n/a n/a n/a [done]
|
||||
----------------------------------------------------------------------------------------------------
|
||||
total - n/a n/a n/a n/a n/a
|
||||
smoke exit=0 seconds=2
|
||||
@@ -0,0 +1,9 @@
|
||||
run finished: done
|
||||
memory s3://sast-memory: ax-e2e-smoke: 0 tasks ingested, 0 already in the index; rows none
|
||||
run ax-e2e-smoke: done (/tmp/claude-1000/-home-mc-Source-OpenUltraSAST/c09e30b2-39a4-4564-bc31-6ea71ffaa198/scratchpad/k8s-impl/results/plane/ax-e2e-smoke)
|
||||
|
||||
TASK MODEL CALLS PROMPT CACHE-HIT OUTPUT USD
|
||||
facts - 0 n/a n/a n/a n/a [done]
|
||||
----------------------------------------------------------------------------------------------------
|
||||
total - n/a n/a n/a n/a n/a
|
||||
smoke exit=0 seconds=26
|
||||
@@ -0,0 +1,9 @@
|
||||
run finished: done
|
||||
memory s3://sast-memory: ax-e2e-smoke: 0 tasks ingested, 0 already in the index; rows none
|
||||
run ax-e2e-smoke: done (/tmp/claude-1000/-home-mc-Source-OpenUltraSAST/c09e30b2-39a4-4564-bc31-6ea71ffaa198/scratchpad/k8s-impl/results/plane/ax-e2e-smoke)
|
||||
|
||||
TASK MODEL CALLS PROMPT CACHE-HIT OUTPUT USD
|
||||
facts - 0 n/a n/a n/a n/a [done]
|
||||
----------------------------------------------------------------------------------------------------
|
||||
total - n/a n/a n/a n/a n/a
|
||||
smoke exit=0 seconds=24
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"store": "s3://sast-memory",
|
||||
"objects_under_run": {
|
||||
"runs/ax-e2e-smoke/facts/done.json": 149,
|
||||
"runs/ax-e2e-smoke/facts/facts.json": 451,
|
||||
"runs/ax-e2e-smoke/facts/output.tar": 10240,
|
||||
"runs/ax-e2e-smoke/facts/summary.json": 238,
|
||||
"runs/ax-e2e-smoke/state.json": 350
|
||||
},
|
||||
"tar_bytes": 10240,
|
||||
"tar_members": {
|
||||
"facts.json": 451,
|
||||
"summary.json": 238,
|
||||
"units.jsonl": 291
|
||||
},
|
||||
"extracted_files": {
|
||||
"facts.json": 451,
|
||||
"units.jsonl": 291,
|
||||
"summary.json": 238
|
||||
},
|
||||
"members_equal_extracted": true,
|
||||
"member_bytes_total": 980,
|
||||
"extracted_bytes_total": 980,
|
||||
"done_json": {
|
||||
"bytes": 10240,
|
||||
"files": 3,
|
||||
"finished": "2026-10-02T19:39:40+00:00",
|
||||
"published": [
|
||||
"facts.json",
|
||||
"summary.json"
|
||||
],
|
||||
"status": "done",
|
||||
"task": "facts"
|
||||
},
|
||||
"tar_put_at_last_modified": "2026-10-02T19:39:38+00:00"
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
== kind cluster 'ousast' (context kind-ousast) + registry localhost:5001 (21:36:18)
|
||||
cluster exists; kept
|
||||
|
||||
== agent substrate (ate-system) (21:36:18)
|
||||
substrate ready; kept
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
ate-api-server-647bb6f8b6-c9k5k 1/1 Running 2 (13h ago) 3d1h
|
||||
ate-api-server-647bb6f8b6-dznjn 1/1 Running 1 (13h ago) 3d1h
|
||||
ate-controller-c95586684-g7nvn 1/1 Running 0 3d3h
|
||||
atelet-4f9c293-9zpfg 1/1 Running 0 3d3h
|
||||
atenet-egress-d74b8f6bf-8zmxj 1/1 Running 0 3d1h
|
||||
atenet-router-54887bc8f9-rqs6g 2/2 Running 2 (13h ago) 3d3h
|
||||
postgres-0 2/2 Running 3 (3d3h ago) 3d3h
|
||||
rustfs-6c4c677cb6-cqvhc 1/1 Running 3 (3d3h ago) 3d3h
|
||||
rustfs-bucket-init-nvfr6 0/1 Completed 0 3d3h
|
||||
|
||||
== egress gateway (atenet-egress, agentgateway variant: one prebuilt image, nothing built) (21:36:18)
|
||||
gateway present; kept
|
||||
|
||||
== ax control plane (ax-system) (21:36:19)
|
||||
ax ready; kept
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
ax-redis-86df786c87-pz5tp 1/1 Running 0 3d3h
|
||||
ax-server-b479bdc76-hwkqb 1/1 Running 0 3d3h
|
||||
ousast-pool-6656b55f5b-5brkn 1/1 Running 0 66m
|
||||
ousast-pool-6656b55f5b-9wbrw 1/1 Running 0 3d3h
|
||||
|
||||
== artifact receiver Service (ousast-receiver.ax-system -> this host) (21:36:19)
|
||||
service/ousast-receiver unchanged
|
||||
endpointslice.discovery.k8s.io/ousast-receiver-host configured
|
||||
|
||||
== worker pool (gVisor): pools.default of the profile (21:36:20)
|
||||
pool ousast-pool exists (2/2 ready); kept
|
||||
pod/ousast-pool-6656b55f5b-5brkn condition met
|
||||
pod/ousast-pool-6656b55f5b-9wbrw condition met
|
||||
|
||||
== runner image localhost:5001/ousast-runner (21:36:21)
|
||||
sha256:0501baf180b29f7aa7bbb8888bebabfc5d62429b7bdaa7f5a62b5b188f8ad5f6
|
||||
localhost:5001/ousast-runner:dev
|
||||
runner image localhost:5001/ousast-runner@sha256:d2a55293999cc34581ef1f02a1ffb0c2dd8fc0cbe001ac952df81600e868f019 -> /home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/kind-images.json
|
||||
re-pin the templates with: ousast plane workspaces ... --runner-image /home/mc/Source/OpenUltraSAST/.claude/worktrees/agent-a7d4724e0c0c407a8/ops/k8s/profiles/kind-images.json (or ousast plane repin, task 4.3)
|
||||
|
||||
== ax CLI from the deployed checkout (a release CLI skews from the server) (21:36:32)
|
||||
ax CLI newer than the checkout; kept
|
||||
|
||||
== smoke task (21:36:32)
|
||||
task.ax.io/ousast-smoke created
|
||||
workspace.ax.io/ousast-smoke-ws unchanged
|
||||
task.ax.io/ousast-smoke resumed
|
||||
NAME ATESPACE PHASE ACTOR WORKER-IP AGE
|
||||
ousast-smoke default Running ousast-smoke 10.244.0.29 4s
|
||||
|
||||
== footprint (21:36:38)
|
||||
total used free shared buff/cache available
|
||||
Mem: 7877 5202 382 63 2903 2674
|
||||
kind-registry 32.45MiB / 7.693GiB 0.00%
|
||||
up.sh exit 0
|
||||
Reference in New Issue
Block a user