WP-0011 T05: live deploy revenue.coulomb.social on railiance01
Image 0.1.1 (schema path fix), OpenBao ESO store, CNPG cluster, TLS, pilot Phase re-registered on public API. Founding token local-only.
This commit is contained in:
parent
592a75a171
commit
ca76fa23a3
13 changed files with 168 additions and 54 deletions
|
|
@ -4,7 +4,10 @@ FROM python:3.12-slim AS runtime
|
|||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
HOME=/home/app
|
||||
HOME=/home/app \
|
||||
TRF_SCHEMAS_DIR=/src/schemas \
|
||||
TRF_SPECS_DIR=/src/specs \
|
||||
TRF_MIGRATIONS_DIR=/src/migrations
|
||||
|
||||
RUN useradd --create-home --home-dir /home/app --uid 10001 app
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ The concept's §13 now defines a **Global Contingency Share Determination Rule**
|
|||
| [TREV-WP-0008](workplans/TREV-WP-0008-governance-and-pilot-rollout.md) | Governance formalization + pilot rollout — **finished**, all 5 tasks done. **T05 accepted 2026-08-05**: first real Phase is `trsl:phase:info-tech-canon-service-surface` (`history/260805-T05-GoLive-info-tech-canon.md`). Org-wide TRSL LICENSE adoption (2026-07-30) was a prior, separate act |
|
||||
| [TREV-WP-0009](workplans/TREV-WP-0009-target-revenue-control-plane.md) | Target Revenue Control Plane — interactive UI for the `binky` tenant, incl. interactive Development Credit entry creation (`specs/TargetRevenueControlPlaneConcept.md`) — **finished**, all 4 tasks done. **T04 (interactive UI, `src/target_revenue/service/control_plane_app.py`) built on vendored `whynot-design` web components** rather than from scratch, per an explicit feasibility check |
|
||||
| [TREV-WP-0010](workplans/TREV-WP-0010-development-effort-calculator.md) | Development Effort Calculator — **finished**, all 3 tasks done. Applied to the three real pilot candidates (`history/260730-EffortCalculator-CandidateApplication.md`) — every calculator-derived Initial Target came out materially lower than the earlier hand-picked placeholders, two of three carrying explicit warnings recommending manual review |
|
||||
| [TREV-WP-0011](workplans/TREV-WP-0011-railiance-reef-deployment.md) | Deploy Trust Service + Control Plane to Railiance (`revenue.coulomb.social`) — **active**; T01–T04 done (Dockerfile, `k8s/railiance/`, ArgoCD Application). **T05** remains: operator DNS/OpenBao/image-push/sync + pilot data migration. Guide: [`docs/deployment.md`](docs/deployment.md) |
|
||||
| [TREV-WP-0011](workplans/TREV-WP-0011-railiance-reef-deployment.md) | Deploy Trust Service + Control Plane — **finished**. Live at [`https://revenue.coulomb.social`](https://revenue.coulomb.social) (UI `/ui/`). Pilot Phase registered on public API. Guide: [`docs/deployment.md`](docs/deployment.md) |
|
||||
| [TREV-WP-0012](workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md) | Phase provenance, ledger reference, and degeneration-policy modeling — **finished**, all 5 tasks done. Decisions (T02–T04) synthesized into [`specs/PhaseProvenanceSpecAddendum.md`](specs/PhaseProvenanceSpecAddendum.md) (T05) — **not yet accepted for implementation**; that's the document to discuss before any schema/UI work is filed as its own workplan |
|
||||
| [TREV-WP-0013](workplans/TREV-WP-0013-remission-credit-automation.md) | Remission Credit automation (degeneration policy execution) — **finished**, all 3 tasks done. Pure `plan_remission` + hosted apply with `system:policy-engine` actor; monthly UTC schedule; Control Plane forecast + apply button |
|
||||
| [TREV-WP-0014](workplans/TREV-WP-0014-control-plane-extensions-breach-attestation-ui.md) | Control Plane UI: Extension Registry, Breach Records, Conversion Attestation — **finished**, all 4 tasks done. Operator+ register extension / Admin canonicalize; breach form with anonymized-by-default; attestation on phase detail |
|
||||
|
|
|
|||
|
|
@ -86,10 +86,10 @@
|
|||
| task | TREV-WP-0010-T01 | done | — | workplans/TREV-WP-0010-development-effort-calculator.md |
|
||||
| task | TREV-WP-0010-T02 | done | — | workplans/TREV-WP-0010-development-effort-calculator.md |
|
||||
| task | TREV-WP-0010-T03 | done | — | workplans/TREV-WP-0010-development-effort-calculator.md |
|
||||
| task | TREV-WP-0011-T01 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T02 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T03 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T04 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T01 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T02 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T03 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T04 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0011-T05 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
|
||||
| task | TREV-WP-0012-T01 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |
|
||||
| task | TREV-WP-0012-T02 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
# Deploying Target Revenue Trust Service + Control Plane
|
||||
|
||||
**Workplan:** `workplans/TREV-WP-0011-railiance-reef-deployment.md`
|
||||
**Public host (target):** `https://revenue.coulomb.social`
|
||||
**Public host (live 2026-08-05):** `https://revenue.coulomb.social`
|
||||
**Control Plane UI:** `https://revenue.coulomb.social/ui/`
|
||||
**Trust Service API:** `https://revenue.coulomb.social/` (e.g. `/phases/...`, `/public-key`)
|
||||
**Trust Service API:** `https://revenue.coulomb.social/` (e.g. `/phases/...`, `/public-key`)
|
||||
**Cluster:** railiance01 (`KUBECONFIG=~/.kube/config-hosteurope`)
|
||||
**Image:** `forgejo.coulomb.social/coulomb/target-revenue:0.1.1`
|
||||
|
||||
This deployment does not by itself authorize new Phases beyond what
|
||||
WP-0008-T05 already recorded. The first pilot Phase is
|
||||
|
|
@ -25,25 +27,25 @@ WP-0008-T05 already recorded. The first pilot Phase is
|
|||
|
||||
---
|
||||
|
||||
## Operator checklist (first bring-up)
|
||||
## Operator checklist (first bring-up) — completed 2026-08-05
|
||||
|
||||
1. **DNS:** point `revenue.coulomb.social` at the Railiance Traefik/ingress (same path as other `*.coulomb.social` hosts on railiance01).
|
||||
2. **OpenBao:** create KV path `workloads/target-revenue/runtime-secrets` with:
|
||||
- `TRF_SIGNING_KEY_HEX` (64 hex chars)
|
||||
- `TRF_CONTROL_PLANE_SECRET_KEY`
|
||||
- `TRF_DATABASE_URL` (trf_app DSN to `target-revenue-pg-rw`)
|
||||
- `TRF_MIGRATE_DATABASE_URL` (trf_owner DSN for migrate/bootstrap Jobs)
|
||||
3. **CNPG bootstrap secrets** in namespace `target-revenue` (before Cluster reconciles):
|
||||
- `target-revenue-pg-credentials` — keys as required by CNPG for owner `trf_owner`
|
||||
- `target-revenue-trf-app-credentials` — password for managed role `trf_app`
|
||||
4. **Image:** build and push
|
||||
`forgejo.coulomb.social/coulomb/target-revenue:0.1.0`
|
||||
(see Dockerfile; bump tag in Deployment + Jobs when releasing).
|
||||
5. **ArgoCD:** apply
|
||||
`railiance-platform/argocd/applications/target-revenue.application.yaml`
|
||||
(once `main` contains `k8s/railiance/`).
|
||||
6. **After migrate + bootstrap Jobs succeed:** pull founding token from bootstrap Job logs (`TOKEN=...`), store offline; sign in at `/ui/login`.
|
||||
7. **Migrate pilot Phase data** from the local go-live instance (`trf-pilot-pg` / `:8765`) if that instance still holds the authoritative ledger — export Manifest + ledger JSON and re-register/append on the public instance (or promote the pilot DB dump deliberately). Do not leave two competing ledgers.
|
||||
Deployed with **kubectl on railiance01** (forgejo/reuse interim model).
|
||||
Coulombcore ArgoCD Application exists for future multi-cluster sync but was
|
||||
**not** the live path for this bring-up.
|
||||
|
||||
1. **DNS:** `revenue.coulomb.social` A-record → railiance01 — done.
|
||||
2. **OpenBao policy + ESO token:**
|
||||
`OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token ./scripts/openbao-eso-token-apply.sh`
|
||||
+ apply `openbao-target-revenue.clustersecretstore.yaml`.
|
||||
3. **CNPG secrets + Cluster**, then write
|
||||
`platform/workloads/target-revenue/runtime-secrets` (signing key, session secret, DSNs).
|
||||
4. **Image:** `docker build/push …:0.1.1` (0.1.0 had schema-path bug; fixed).
|
||||
5. **`kubectl apply -k k8s/railiance/`** on railiance01.
|
||||
6. **Founding token:** bootstrap Job log →
|
||||
`~/.config/target-revenue/revenue-coulomb-binky.token` (chmod 600; never commit).
|
||||
7. **Phase POR:** re-registered
|
||||
`trsl:phase:info-tech-canon-service-surface` on the public API; local
|
||||
`trf-pilot-pg` is superseded (ledger was empty, so no entry migration).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -82,18 +82,18 @@ from day one, not merely a git file:
|
|||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Trust Service API | `http://127.0.0.1:8765` (local pilot; not public) |
|
||||
| Postgres | Docker container `trf-pilot-pg`, port `127.0.0.1:55432`, volume `trf-pilot-pgdata` |
|
||||
| Migrations | 0001–0007 applied |
|
||||
| Licensor credential | `binky` / label `pilot-founder` (token in `~/.config/target-revenue/pilot-binky.token`, never committed) |
|
||||
| Registration time (t0) | 2026-08-05T13:57:18Z (host `registered_at`) |
|
||||
| Trust Service API (initial) | `http://127.0.0.1:8765` (local pilot; used only until public deploy) |
|
||||
| Trust Service API (**POR, 2026-08-05 later same day**) | `https://revenue.coulomb.social` (railiance01; WP-0011 T05) |
|
||||
| Control Plane | `https://revenue.coulomb.social/ui/` |
|
||||
| Postgres (POR) | CNPG `target-revenue-pg` in namespace `target-revenue` |
|
||||
| Licensor credential | `binky` / label `founding-admin` (token in `~/.config/target-revenue/revenue-coulomb-binky.token`, never committed) |
|
||||
| Registration time (t0) on POR | 2026-08-05T15:55:52Z (public re-registration; local pilot ledger was empty) |
|
||||
| Extension registered | `trsl:extension:development-license@1.0` |
|
||||
| Ledger at registration | empty; Outstanding Target = 141,800 EUR |
|
||||
|
||||
When WP-0011 lands a public instance, this Phase's Manifest and ledger
|
||||
export must be migrated or re-anchored deliberately — the git-committed
|
||||
Manifest remains the durable declaration either way
|
||||
(`specs/TrustServiceOnboarding.md` §1).
|
||||
Git-committed Manifest remains the durable declaration
|
||||
(`specs/TrustServiceOnboarding.md` §1). Public Trust Service is the live
|
||||
ledger authority after WP-0011 T05.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ spec:
|
|||
fsGroup: 10001
|
||||
containers:
|
||||
- name: bootstrap
|
||||
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.0
|
||||
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TRF_BOOTSTRAP_BINKY
|
||||
|
|
|
|||
|
|
@ -25,12 +25,16 @@ spec:
|
|||
fsGroup: 10001
|
||||
containers:
|
||||
- name: target-revenue
|
||||
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.0
|
||||
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
env:
|
||||
- name: TRF_SCHEMAS_DIR
|
||||
value: /src/schemas
|
||||
- name: TRF_SPECS_DIR
|
||||
value: /src/specs
|
||||
- name: TRF_DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ metadata:
|
|||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: openbao
|
||||
# Railiance01 interim store (token auth), not coulombcore's openbao store.
|
||||
name: openbao-target-revenue
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: target-revenue-runtime
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ spec:
|
|||
fsGroup: 10001
|
||||
containers:
|
||||
- name: migrate
|
||||
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.0
|
||||
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TRF_RUN_MIGRATIONS
|
||||
|
|
|
|||
60
scripts/openbao-eso-token-apply.sh
Executable file
60
scripts/openbao-eso-token-apply.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env bash
|
||||
# Mint a read-limited OpenBao token and store it on railiance01 for
|
||||
# ClusterSecretStore openbao-target-revenue (WP-0011).
|
||||
set -euo pipefail
|
||||
|
||||
POLICY_NAME="${OPENBAO_TARGET_REVENUE_POLICY:-workload-kv-read-target-revenue-runtime}"
|
||||
POLICY_FILE="${OPENBAO_TARGET_REVENUE_POLICY_FILE:-$HOME/railiance-platform/openbao/policies/workload-kv-read-target-revenue-runtime.hcl}"
|
||||
BAO_ADDR="${BAO_ADDR:-https://bao.coulomb.social}"
|
||||
RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-$HOME/.kube/config-hosteurope}"
|
||||
SECRET_NAME="${OPENBAO_TARGET_REVENUE_ESO_SECRET:-openbao-target-revenue-eso-token}"
|
||||
SECRET_NS="${OPENBAO_TARGET_REVENUE_ESO_NAMESPACE:-external-secrets}"
|
||||
|
||||
if ! command -v bao >/dev/null 2>&1; then
|
||||
echo "ERROR: bao CLI not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$POLICY_FILE" ]]; then
|
||||
echo "ERROR: policy file missing: $POLICY_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OpenBao addr: $BAO_ADDR"
|
||||
echo "Policy: $POLICY_NAME"
|
||||
|
||||
if [[ -n "${BAO_TOKEN:-}" ]]; then
|
||||
:
|
||||
elif [[ -n "${OPENBAO_TOKEN_FILE:-}" && -f "${OPENBAO_TOKEN_FILE}" ]]; then
|
||||
BAO_TOKEN="$(head -n 1 "${OPENBAO_TOKEN_FILE}")"
|
||||
else
|
||||
read -r -s -p "OpenBao operator token: " BAO_TOKEN
|
||||
echo >&2
|
||||
fi
|
||||
if [[ -z "${BAO_TOKEN:-}" ]]; then
|
||||
echo "ERROR: empty OpenBao token" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export BAO_ADDR BAO_TOKEN
|
||||
|
||||
health="$(curl -fsS "$BAO_ADDR/v1/sys/health")"
|
||||
if echo "$health" | grep -q '"sealed":true'; then
|
||||
echo "ERROR: OpenBao at $BAO_ADDR reports sealed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bao policy write "$POLICY_NAME" "$POLICY_FILE"
|
||||
token_json="$(bao token create -policy="$POLICY_NAME" \
|
||||
-display-name="eso-target-revenue-runtime" -period=720h -format=json)"
|
||||
token="$(python3 -c "import json,sys; print(json.load(sys.stdin)['auth']['client_token'])" <<<"$token_json")"
|
||||
|
||||
KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl create namespace "$SECRET_NS" --dry-run=client -o yaml \
|
||||
| KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl apply -f -
|
||||
KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl create secret generic "$SECRET_NAME" \
|
||||
--namespace "$SECRET_NS" \
|
||||
--from-literal=token="$token" \
|
||||
--dry-run=client -o yaml \
|
||||
| KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl apply -f -
|
||||
|
||||
unset BAO_TOKEN token token_json
|
||||
echo "ok: applied $SECRET_NS/$SECRET_NAME on railiance01 (token not printed)"
|
||||
|
|
@ -10,13 +10,32 @@ small markdown library for six profile pages and one policy page.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import markdown
|
||||
import yaml
|
||||
|
||||
_SPECS_DIR = Path(__file__).resolve().parents[3] / "specs"
|
||||
|
||||
def _resolve_specs_dir() -> Path:
|
||||
if env := os.environ.get("TRF_SPECS_DIR"):
|
||||
return Path(env)
|
||||
here = Path(__file__).resolve()
|
||||
candidates = [
|
||||
here.parents[3] / "specs", # monorepo: .../target-revenue/specs
|
||||
Path("/src/specs"), # container image layout
|
||||
]
|
||||
for candidate in candidates:
|
||||
if candidate.is_dir():
|
||||
return candidate
|
||||
raise FileNotFoundError(
|
||||
"cannot find TRF specs directory; set TRF_SPECS_DIR or install "
|
||||
"specs at /src/specs"
|
||||
)
|
||||
|
||||
|
||||
_SPECS_DIR = _resolve_specs_dir()
|
||||
|
||||
# kind -> subdirectory name under specs/. Only these two exist today
|
||||
# (WP-0015-T02); a third kind (e.g. "calculators") can be added here if
|
||||
|
|
|
|||
|
|
@ -14,12 +14,33 @@ at import/call time (TSD §6.1 determinism).
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import jsonschema
|
||||
|
||||
_SCHEMAS_DIR = Path(__file__).resolve().parents[2] / "schemas"
|
||||
|
||||
def _resolve_schemas_dir() -> Path:
|
||||
"""Locate `schemas/` in monorepo, container, or packaged installs."""
|
||||
if env := os.environ.get("TRF_SCHEMAS_DIR"):
|
||||
return Path(env)
|
||||
here = Path(__file__).resolve()
|
||||
candidates = [
|
||||
here.parent / "schemas", # if schemas are package data
|
||||
here.parents[2] / "schemas", # repo: src/target_revenue/../..
|
||||
Path("/src/schemas"), # container image layout (WP-0011 Dockerfile)
|
||||
]
|
||||
for candidate in candidates:
|
||||
if candidate.is_dir():
|
||||
return candidate
|
||||
raise FileNotFoundError(
|
||||
"cannot find TRF schemas directory; set TRF_SCHEMAS_DIR or install "
|
||||
"schemas next to the package / at /src/schemas"
|
||||
)
|
||||
|
||||
|
||||
_SCHEMAS_DIR = _resolve_schemas_dir()
|
||||
|
||||
# TSD §6.2/§3.3: an extension's allocation.rule must not redefine these.
|
||||
CORE_TERMS = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ type: workplan
|
|||
title: "Deploy Trust Service and Control Plane to the Railiance reef"
|
||||
domain: infotech
|
||||
repo: target-revenue
|
||||
status: active
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: infotech
|
||||
created: "2026-07-30"
|
||||
|
|
@ -100,23 +100,27 @@ bootstrap Jobs. ArgoCD Application:
|
|||
|
||||
```task
|
||||
id: TREV-WP-0011-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "37847415-19c4-43b5-8b0d-de7a4211bfdd"
|
||||
```
|
||||
|
||||
**Bootstrap and smoke-test the live deployment.**
|
||||
|
||||
**Remaining operator work** (not fully automatable from this workstation
|
||||
without cluster credentials + OpenBao write + DNS + image push):
|
||||
**Result (2026-08-05):** Live on **railiance01** (not coulombcore ArgoCD —
|
||||
same interim model as forgejo/reuse; DNS points to Railiance01):
|
||||
|
||||
1. DNS A/CNAME for `revenue.coulomb.social`
|
||||
2. Seed OpenBao runtime secrets + CNPG bootstrap Secrets
|
||||
3. `docker build/push` image `0.1.0`
|
||||
4. Commit/push Application + app manifests; wait for ArgoCD sync
|
||||
5. Confirm TLS + `/ui/login` + `/healthz`
|
||||
6. Migrate pilot Phase ledger from local `trf-pilot-pg` if public instance
|
||||
becomes the new authority
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| DNS | `revenue.coulomb.social` → railiance01 (operator) |
|
||||
| Image | `forgejo.coulomb.social/coulomb/target-revenue:0.1.1` |
|
||||
| OpenBao ESO | `ClusterSecretStore/openbao-target-revenue` Ready; runtime SecretSynced |
|
||||
| CNPG | `target-revenue-pg` healthy |
|
||||
| Migrations + bootstrap Jobs | complete; founding token local-only at `~/.config/target-revenue/revenue-coulomb-binky.token` |
|
||||
| TLS | cert-manager Ready (`target-revenue-tls`) |
|
||||
| `GET /healthz` | 200 |
|
||||
| `GET /ui/login` | 200 |
|
||||
| Pilot Phase re-registered | `trsl:phase:info-tech-canon-service-surface` on public API (empty ledger; public instance is now POR — local `trf-pilot-pg` is superseded) |
|
||||
|
||||
Docs ready: `docs/deployment.md`. No production smoke Phase will be
|
||||
registered (append-only; pilot Phase already exists).
|
||||
Schema-path fix in 0.1.1 (`TRF_SCHEMAS_DIR` / validation resolve) after
|
||||
first image failed package-relative schema loads.
|
||||
Loading…
Add table
Add a link
Reference in a new issue