diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/bin/casan b/bin/casan new file mode 100755 index 0000000..60d2da0 --- /dev/null +++ b/bin/casan @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# CASAN — lightweight CLI wrapper (Level 1 Core Harness). +# +# Locates the harness package relative to this script (works both in the full +# source hub and in an extracted casan-core / casan-devkit bundle) and dispatches +# to the harness bash entrypoints. No dependencies beyond bash + python3. +set -uo pipefail + +# --- locate the harness root (dir containing scripts/bash/casan-harness.sh) ---- +_self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_find_harness() { + # 1) sibling packages/casan-harness (source hub or bundle root layout) + local c + for c in "$_self/../packages/casan-harness" "$_self/../casan-harness" "$_self/packages/casan-harness"; do + [[ -f "$c/scripts/bash/casan-harness.sh" ]] && { (cd "$c" && pwd); return 0; } + done + # 2) walk up looking for it + local d="$_self" + while [[ "$d" != "/" ]]; do + [[ -f "$d/packages/casan-harness/scripts/bash/casan-harness.sh" ]] && { echo "$d/packages/casan-harness"; return 0; } + d="$(dirname "$d")" + done + return 1 +} +HARNESS="${CASAN_HARNESS_ROOT:-$(_find_harness)}" +if [[ -z "${HARNESS:-}" || ! -d "$HARNESS" ]]; then + echo "casan: cannot locate packages/casan-harness (set CASAN_HARNESS_ROOT)" >&2 + exit 1 +fi +BASH_DIR="$HARNESS/scripts/bash" +TESTS_DIR="$HARNESS/tests" +VERSION_FILE="$_self/../VERSION" +[[ -f "$VERSION_FILE" ]] || VERSION_FILE="$HARNESS/../../VERSION" + +version() { printf 'casan %s\n' "$( [[ -f "$VERSION_FILE" ]] && cat "$VERSION_FILE" || echo 'unknown')"; } + +usage() { + cat < [args] + +Commands: + run [action] [-- cmd...] Run a step through the harness (H4→H5→H6→exec→H4-out) + gate Run the full CI harness gate (all suites) + test Run the core harness test suite (run-casan4) + verify Verify audit chain + tool audit + policy bundle + reuse Verify multi-project harness reuse (registry) + dashboard [port] Serve the AgentOps dashboard (default 8787) + version Print version + help This help + +Env: CASAN_HARNESS_ROOT overrides harness location. +Harness: $HARNESS +EOF +} + +cmd="${1:-help}"; shift || true +case "$cmd" in + run) exec bash "$BASH_DIR/casan-harness.sh" "$@" ;; + gate) exec bash "$BASH_DIR/ci-harness-gate.sh" "$@" ;; + test) exec bash "$TESTS_DIR/run-casan4-harness-tests.sh" "$@" ;; + verify) + rc=0 + bash "$BASH_DIR/verify-audit-chain.sh" "$@" || rc=$? + bash "$BASH_DIR/verify-tool-audit.sh" || rc=$? + bash "$BASH_DIR/sign-policy-bundle.sh" verify || rc=$? + exit $rc ;; + reuse) exec bash "$BASH_DIR/verify-harness-reuse.sh" "$@" ;; + dashboard) exec bash "$BASH_DIR/dashboard-serve.sh" "$@" ;; + version|-v|--version) version ;; + help|-h|--help) usage ;; + *) echo "casan: unknown command '$cmd'" >&2; usage >&2; exit 64 ;; +esac diff --git a/docs/packaging/ADOPTION_GUIDE.md b/docs/packaging/ADOPTION_GUIDE.md new file mode 100644 index 0000000..673b275 --- /dev/null +++ b/docs/packaging/ADOPTION_GUIDE.md @@ -0,0 +1,55 @@ +# CASAN Adoption Guide + +How a downstream project adopts the CASAN governance harness. Adoption is **config + +domain only** — you never edit gate logic (H1→H7). + +## Option A — DevKit install (recommended) +From a CASAN source hub or an extracted `casan-devkit` bundle: +```bash +packages/casan-devkit/install.sh --target ../my-project --project ticketing --domain "Ticketing" +``` +This copies the core harness + `bin/casan` into `../my-project`, scaffolds +`apps/ticketing/domain/` from the domain-pack template, adds `.gitea/workflows/casan-ci.yml`, +and registers the project in `project-registry.json`. + +## Option B — Core tarball (harness-only / CI gate) +```bash +tar -xzf casan-core-v1.0.0.tar.gz +cp -R casan-core-v1.0.0/{packages,bin,VERSION} /path/to/project/ +``` +Then create `apps//domain/` yourself (see `DOMAIN_PACK_GUIDE.md`). + +## Option C — Docker (no install into repo) +```bash +docker run --rm -v "$PWD":/workspace -w /workspace casan-harness:1.0.0 casan gate +``` +See `DOCKER_GUIDE.md`. + +## After install +1. Requirement → `apps//domain/input/requirement.md` (keep the `| FR-xx |` table). +2. Golden baseline → `apps//domain/golden-runs/.golden.txt`. +3. Corpus → `apps//domain/corpus/` (redteam + benign) for H4 scoring. +4. Requirement→code→test map → `apps//domain/traceability-map.json`. +5. Run: + ```bash + CASAN_DOMAIN_ROOT=apps//domain bin/casan gate # full governance gate + bin/casan run in.txt out.txt my_step -- # one governed step + bin/casan reuse # HARNESS_REUSE_VALID + ``` + +## Path model (what lives where) +- **Harness code** → `packages/casan-harness/` (never edited by adopters). +- **Your domain data** → `apps//domain/` (via `CASAN_DOMAIN_ROOT`). +- **Runtime state** → `.specify/` (logs, audit, governance — created on first run). +Paths resolve via `packages/casan-harness/scripts/bash/casan-paths.sh` (marker walk-up: +`.specify` or `packages/casan-harness`), so a freshly-extracted bundle works immediately. + +## Proving reuse +Two+ projects sharing the same harness package/version → `bin/casan reuse` prints +`HARNESS_REUSE_VALID ... project_count=N`. This is the evidence that the harness is genuinely +reusable, not copy-pasted. See `docs/plans/CASAN_PLAN_06_ONBOARD.md`. + +## Upgrading +Re-run `install.sh` from a newer DevKit (or re-extract a newer core tarball). Your +`apps//domain/` and `.specify/` state are untouched — only `packages/casan-harness/` ++ `bin/casan` are replaced. Keep `harness_version` aligned across projects for reuse to count. diff --git a/docs/packaging/CASAN_PACKAGING_PLAN.md b/docs/packaging/CASAN_PACKAGING_PLAN.md new file mode 100644 index 0000000..23ac5cf --- /dev/null +++ b/docs/packaging/CASAN_PACKAGING_PLAN.md @@ -0,0 +1,67 @@ +# CASAN Packaging Plan — Level-Based Source Hub + +CASAN is **not** packaged as "core only". This repository is a **reusable source hub** that +holds (or scaffolds) all major CASAN components, and **releases are split by level** so a +downstream project adopts only the level it needs. Single source of truth for bundle +contents + maturity: [`packaging/levels.json`](../../packaging/levels.json). + +## The four levels + +| Lvl | Package names | Status | What it is | +|---|---|:--:|---| +| **1 — Core Harness** | `casan-core`, `casan-harness` | ✅ implemented | H1–H7 harness, security + action gates, evidence pack, audit, cost/telemetry, hardening tests, policy/config defaults, `bin/casan` CLI | +| **2 — DevKit / Adoption Kit** | `casan-devkit`, `casan-project-kit` | ✅ implemented | Level 1 + project templates, domain-pack scaffold, Gitea workflow template, harness Dockerfile, install script, adoption/CI/domain-pack guides | +| **3 — Platform Components** | `casan-platform`, `casan-control-panel` | 🟡 preview | Control Panel, Dashboard, Evidence/Attack/Run-History viewers, read-only Ask CASAN, Gitea webhook. **Only the AgentOps dashboard exists today.** | +| **4 — Enterprise / Governed Console** | `casan-enterprise`, `casan-governed-console` | 📋 future | Governed Chat Console, Prompt Router, Model-Provider Mgmt, Operator/Codegen modes, Agent/Skill Registry, RBAC, approval, tenant isolation, KMS/WORM, policy versioning | + +Levels are cumulative: DevKit extends Core, Platform extends DevKit, Enterprise extends +Platform. + +## Packaging principle +The source hub may contain all levels, **but releases must be split**. Do NOT force a +downstream project to install everything. A level that is not implemented must **fail +clearly** or be stamped **PREVIEW/INCOMPLETE** — never a fake-complete package. + +## Release artifacts +Built by [`scripts/package-release.sh`](../../scripts/package-release.sh) into `dist/`: + +| Command | Artifact | Status | +|---|---|:--:| +| `package-release.sh core` | `casan-core-vX.Y.Z.tar.gz` | ✅ builds | +| `package-release.sh devkit` | `casan-devkit-vX.Y.Z.tar.gz` | ✅ builds | +| `package-release.sh platform` | `casan-platform-preview-vX.Y.Z.tar.gz` | 🟡 preview (stamped) | +| `package-release.sh all-in-one-demo` | `casan-all-in-one-demo-vX.Y.Z.tar.gz` | ✅ builds (full runnable snapshot) | +| `package-release.sh enterprise` | — | 📋 refused (exit 3, future) | + +Docker images (see `DOCKER_GUIDE.md`): `casan-harness:X.Y.Z` ✅ · `casan-platform:X.Y.Z` 🟡 preview · `casan-enterprise:X.Y.Z` 📋 future. + +Every bundle carries `BUNDLE-MANIFEST.txt` + `SHA256SUMS`; preview bundles also carry +`PREVIEW-INCOMPLETE.txt`. + +## Who adopts what +- **Governance-harness-only / BJT initial / CI gate** → `casan-core`. +- **New project adopting CASAN** → `casan-devkit` (install.sh scaffolds domain + CI). +- **Want dashboards/visibility** → `casan-platform` (preview; dashboard today). +- **Enterprise governed console** → future; building blocks (RBAC/tenant/KMS/WORM/approval) + already live in core. + +## Repository map +``` +packages/casan-harness/ # L1 core (implemented) +packages/casan-devkit/ # L2 adoption kit (implemented) +packages/casan-platform/ # L3 structure-only (preview; README) +packages/casan-enterprise/ # L4 structure-only (future; README) +bin/casan # CLI +scripts/package-release.sh # release packager +packaging/levels.json # bundle contents + maturity (source of truth) +docs/packaging/ # this plan + adoption/CI/domain-pack/gitea/docker guides +``` + +## Implemented now vs future +- **Implemented now:** Level 1 Core packaging, Level 2 DevKit packaging, release script, + `bin/casan`, templates, docs, all-in-one-demo bundle. +- **Structure + docs only:** Level 3 Platform (dashboard exists; rest scaffolded), + Level 4 Enterprise (RBAC/tenant/KMS/WORM/approval exist in core; governed console not built). +- **Not built in this task (do not assume present):** Governed Chat Console, Prompt Mode + Router, Model Provider Management, Operator/Codegen modes, Agent/Skill Registry, RBAC/ + tenant *console* UX. diff --git a/docs/packaging/CI_GUIDE.md b/docs/packaging/CI_GUIDE.md new file mode 100644 index 0000000..d5a65de --- /dev/null +++ b/docs/packaging/CI_GUIDE.md @@ -0,0 +1,47 @@ +# CASAN CI Guide (Gitea Actions) + +Wire the CASAN governance gate into your project's CI so every push/PR is governed. + +## 1. Add the workflow +DevKit `install.sh` already drops `.gitea/workflows/casan-ci.yml`. To add manually, copy the +template: +```bash +cp packages/casan-devkit/templates/gitea-workflow/ci.yml .gitea/workflows/casan-ci.yml +``` + +## 2. What it runs +```yaml +- bash packages/casan-harness/scripts/bash/ci-harness-gate.sh # all governance suites +- bash packages/casan-harness/scripts/bash/verify-audit-chain.sh +- bash packages/casan-harness/scripts/bash/sign-policy-bundle.sh verify +``` + +## 3. Environment knobs +| Var | Default | Use | +|---|---|---| +| `CASAN_CI_RUN_FRONTEND` | 0 | 1 if you have a frontend workspace | +| `CASAN_CI_RUN_BACKEND` | 0 | 1 to run backend tests | +| `CASAN_CI_RUN_INFRA_LAB` | 0 | 1 to run the Docker infra lab | +| `CASAN_CI_STEP_TIMEOUT_SEC` | 600 | raise to 1200 — some suites are model-backed and run ~450s; 600 flakes under load | +| `CASAN_CI_SUITE_FILTER` | — | regex to run a subset of suites | +| `CASAN_DOMAIN_ROOT` | apps/okr/domain | point at your project's domain | + +## 4. Runner +Uses `runs-on: ci-runner` (a self-hosted Gitea Actions runner). To set one up on your host, +see `.gitea/vps-setup-runbook.md` and `scripts/setup-ci-runner.sh`. The runner needs +`bash`, `python3`, `openssl` (and `node`/`npm` only if you enable frontend/backend tests). + +## 5. Expected result +`CI_GATE_SUMMARY PASS= FAIL=0 SKIP=`. Any FAIL fails the job (exit 1). The gate is +**fail-closed**: missing signatures/policy in enforced mode (`CASAN_PROFILE=prod` or +`CASAN_VERIFY_STRICT=1`) also fail. + +## 6. This repo's own CI +The source hub's active pipeline is `.gitea/workflows/ci.yml` (frontend tests → security +gate → deploy). It already runs the gate from the promoted root layout +(`packages/casan-harness/...`). Use it as a worked example. + +## Tips +- Keep the gate green as an invariant; every new control must ship a fail-able adversarial test. +- Do not stage runtime log artifacts (`.specify/logs`, evidence) produced by gate runs. +- For a quick local pre-push check: `bin/casan gate` (or a filtered subset). diff --git a/docs/packaging/DOCKER_GUIDE.md b/docs/packaging/DOCKER_GUIDE.md new file mode 100644 index 0000000..0a00f2e --- /dev/null +++ b/docs/packaging/DOCKER_GUIDE.md @@ -0,0 +1,50 @@ +# CASAN Docker Guide + +Run the CASAN governance harness as a container against any mounted repo — no install into +the target project. + +## Image: `casan-harness` (Level 1/2) +Dockerfile: [`packages/casan-devkit/Dockerfile.harness`](../../packages/casan-devkit/Dockerfile.harness). +Minimal Debian + bash + python3 + openssl + git. Ships the core harness + `bin/casan`. + +### Build +```bash +V=$(cat VERSION) +docker build -f packages/casan-devkit/Dockerfile.harness -t casan-harness:$V . +``` + +### Run the gate on a project +```bash +docker run --rm -v "$PWD":/workspace -w /workspace casan-harness:1.0.0 gate +# (ENTRYPOINT is `casan`, so the CMD is a casan subcommand) +docker run --rm -v "$PWD":/workspace casan-harness:1.0.0 reuse +docker run --rm -v "$PWD":/workspace casan-harness:1.0.0 \ + run in.txt out.txt my_step -- echo hello +``` +The harness inside the image lives at `/opt/casan` (`CASAN_HARNESS_ROOT` preset); your +project is mounted at `/workspace`. Domain data is read from the mounted repo +(`apps//domain` via `CASAN_DOMAIN_ROOT`). + +### With a domain root +```bash +docker run --rm -v "$PWD":/workspace -w /workspace \ + -e CASAN_DOMAIN_ROOT=apps/ticketing/domain casan-harness:1.0.0 gate +``` + +## Notes +- **State** (`.specify/logs`, audit) is written under `/workspace` (your mounted repo), so it + persists on the host and is inspectable after the run. +- **Enforced mode:** add `-e CASAN_PROFILE=prod` (or `-e CASAN_VERIFY_STRICT=1`) to make + missing signatures fail-closed. +- **No app runtime:** this image runs the *harness*, not the OKR app. For the app, use the + project's own `Dockerfile.backend` / `Dockerfile.frontend` + `docker-compose.prod.yml`. + +## Other images +| Image | Status | Notes | +|---|:--:|---| +| `casan-harness:X.Y.Z` | ✅ | this guide | +| `casan-platform:X.Y.Z` | 🟡 preview | dashboard only today; build from platform preview when needed | +| `casan-enterprise:X.Y.Z` | 📋 future | not built — governed console does not exist yet | + +## Publish to Gitea container registry +See `GITEA_PACKAGE_GUIDE.md` §3 (`docker login` + `docker push` to the Gitea registry). diff --git a/docs/packaging/DOMAIN_PACK_GUIDE.md b/docs/packaging/DOMAIN_PACK_GUIDE.md new file mode 100644 index 0000000..a3119cc --- /dev/null +++ b/docs/packaging/DOMAIN_PACK_GUIDE.md @@ -0,0 +1,52 @@ +# CASAN Domain Pack Guide + +A **Domain Pack** is the per-project data the harness needs to govern YOUR domain. It lives +at `apps//domain/` and is selected via `CASAN_DOMAIN_ROOT`. The harness code +(`packages/casan-harness/`) never contains domain data — this is what makes it reusable. + +Scaffold template: `packages/casan-devkit/templates/domain-pack/`. + +## Layout +``` +apps//domain/ +├── domain-pack.yaml # manifest describing this pack +├── input/ +│ ├── requirement.md # FR-xx table drives the traceability gate +│ └── architecture.md # optional tech-stack/context +├── golden-runs/ +│ └── .golden.txt # baseline for drift detection (H7) +├── corpus/ +│ ├── redteam-corpus.jsonl # attack vectors (H4 recall) — one JSON per line +│ ├── redteam-vectors.jsonl # vectors for benign-fp-report / redteam metrics +│ └── benign-corpus/*.txt # benign samples (H4 false-positive budget) +└── traceability-map.json # FR-xx → code files + test files (+ optional symbols/lines) +``` + +## Each piece +- **input/requirement.md** — must contain a `| FR-xx | ... |` table. The traceability gate + (Plan-10) requires every `FR-xx` to map to ≥1 existing code file and ≥1 test file. No + secrets/PII (the H4 input scan blocks them). +- **golden-runs/** — a canonical "good" artifact per pipeline output. `drift-detect` compares + a run against it; similarity 1.0 = no drift. Real drift detection is proven separately. +- **corpus/** — red-team attack vectors (scored for H4 recall) + a benign corpus (bounds the + false-positive rate). Domain-specific injections make the H4 score meaningful. +- **traceability-map.json** — `{"FR-01": {"name": "...", "code": ["backend/src/...", {"file":"...","symbols":["Foo"],"lines":[12]}], "tests": ["..."]}}`. Symbol/line refs are + optional but tighten the gate. +- **domain-pack.yaml** — documents the above + optional threshold overrides. + +## Wire it up +```bash +export CASAN_DOMAIN_ROOT=apps//domain +bin/casan gate # runs domain-dependent suites against your pack +``` +`domain_root` is also recorded per-project in `packages/casan-harness/level5/project-registry.json` +so `bin/casan reuse` can prove multi-project reuse. + +## Reference example +The OKR app's own pack is the worked example: `apps/okr/domain/` (golden-runs, corpus, +input, traceability-map). Copy its shape for your domain. + +## Toward Plan-12 (Domain Pack SDK) +Today a pack is a directory + `CASAN_DOMAIN_ROOT`. Plan-12 will make it fully declarative +(register a pack by manifest, no manual wiring). The `domain-pack.yaml` here is the seed of +that manifest. diff --git a/docs/packaging/GITEA_PACKAGE_GUIDE.md b/docs/packaging/GITEA_PACKAGE_GUIDE.md new file mode 100644 index 0000000..7b3fa3b --- /dev/null +++ b/docs/packaging/GITEA_PACKAGE_GUIDE.md @@ -0,0 +1,73 @@ +# CASAN on Gitea — Source Hub + Package Registry + +This repo uses the existing Gitea server (`ssh://git@161.33.139.73:2222/admin/casan5.git`) +as the **source hub**. Gitea also ships a **package registry** (generic files) and a +**container registry**, so CASAN release bundles and Docker images can live next to the +source. This guide shows the release flow. No secrets are committed; use a Gitea token/PAT. + +> Gitea host below is written as `$GITEA` (e.g. `http://161.33.139.73:3000`). Set +> `GITEA_TOKEN` to a personal access token with `write:package` scope. Adjust `admin`/`casan5` +> to your org/repo. + +## 1. Build the bundles +```bash +scripts/package-release.sh core +scripts/package-release.sh devkit +scripts/package-release.sh platform # preview +scripts/package-release.sh all-in-one-demo +# enterprise → intentionally refused (future) +ls dist/ # *.tar.gz + *.sha256 +``` + +## 2. Publish tarballs to the Gitea generic package registry +Endpoint: `PUT $GITEA/api/packages/{owner}/generic/{name}/{version}/{file}` +```bash +GITEA=http://161.33.139.73:3000; OWNER=admin; V=$(cat VERSION) +for lvl in core devkit; do + f="dist/casan-$lvl-v$V.tar.gz" + curl -fsSL -XPUT -H "Authorization: token $GITEA_TOKEN" \ + --upload-file "$f" \ + "$GITEA/api/packages/$OWNER/generic/casan-$lvl/$V/$(basename "$f")" + curl -fsSL -XPUT -H "Authorization: token $GITEA_TOKEN" \ + --upload-file "$f.sha256" \ + "$GITEA/api/packages/$OWNER/generic/casan-$lvl/$V/$(basename "$f").sha256" +done +# platform is a preview artifact: +curl -fsSL -XPUT -H "Authorization: token $GITEA_TOKEN" \ + --upload-file "dist/casan-platform-preview-v$V.tar.gz" \ + "$GITEA/api/packages/$OWNER/generic/casan-platform/$V-preview/casan-platform-preview-v$V.tar.gz" +``` +Downstream then pulls: +```bash +curl -fsSL -H "Authorization: token $TOKEN" \ + "$GITEA/api/packages/admin/generic/casan-core/1.0.0/casan-core-v1.0.0.tar.gz" -o casan-core.tar.gz +``` + +## 3. Publish Docker images to the Gitea container registry +```bash +V=$(cat VERSION) +docker build -f packages/casan-devkit/Dockerfile.harness -t "$REG/admin/casan-harness:$V" . +echo "$GITEA_TOKEN" | docker login "$REG" -u admin --password-stdin # REG=161.33.139.73:3000 +docker push "$REG/admin/casan-harness:$V" +``` +See `DOCKER_GUIDE.md` for image details. `casan-platform:$V` is preview; `casan-enterprise:$V` +is future (do not publish). + +## 4. Attach bundles to a Gitea Release (optional, human-facing) +Create a tag + release via the API and upload the tarballs as release attachments: +```bash +# create release for tag vX.Y.Z, then: +curl -fsSL -XPOST -H "Authorization: token $GITEA_TOKEN" \ + -F "attachment=@dist/casan-devkit-v$V.tar.gz" \ + "$GITEA/api/v1/repos/admin/casan5/releases/{release_id}/assets?name=casan-devkit-v$V.tar.gz" +``` + +## 5. Recommended cadence +- Tag `vX.Y.Z` on `main` → CI green → build bundles → publish `core` + `devkit` (always), + `platform` as `-preview`, `all-in-one-demo` for demos. Never publish `enterprise`. +- Keep `VERSION` and `packages/casan-harness/level5/harness-package.json` version in lockstep. + +## Automating in CI +Add a release job to `.gitea/workflows/` that runs after the gate, calls +`scripts/package-release.sh`, and does the `curl` uploads with `${{ secrets.GITEA_TOKEN }}`. +Keep it gated on tags (`on: push: tags: ['v*']`) so ordinary pushes don't publish. diff --git a/packages/casan-devkit/Dockerfile.harness b/packages/casan-devkit/Dockerfile.harness new file mode 100644 index 0000000..6a082d5 --- /dev/null +++ b/packages/casan-devkit/Dockerfile.harness @@ -0,0 +1,25 @@ +# CASAN harness runtime image (Level 1/2). Runs the governance gate on a mounted repo. +# Minimal: bash + python3 + openssl (+ git for repo-root marker). No app runtime. +# +# Build (from source hub or an extracted casan-devkit bundle): +# docker build -f packages/casan-devkit/Dockerfile.harness -t casan-harness:1.0.0 . +# Run the gate against a project mounted at /workspace: +# docker run --rm -v "$PWD":/workspace -w /workspace casan-harness:1.0.0 casan gate +FROM debian:bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends bash python3 openssl git ca-certificates rsync \ + && rm -rf /var/lib/apt/lists/* + +# Ship the core harness + CLI inside the image. +WORKDIR /opt/casan +COPY packages/casan-harness/ ./packages/casan-harness/ +COPY bin/casan ./bin/casan +COPY VERSION ./VERSION +RUN chmod +x ./bin/casan && ln -s /opt/casan/bin/casan /usr/local/bin/casan + +# Default working dir is the mounted project. +WORKDIR /workspace +ENV CASAN_HARNESS_ROOT=/opt/casan/packages/casan-harness +ENTRYPOINT ["casan"] +CMD ["help"] diff --git a/packages/casan-devkit/README.md b/packages/casan-devkit/README.md new file mode 100644 index 0000000..1ac3863 --- /dev/null +++ b/packages/casan-devkit/README.md @@ -0,0 +1,31 @@ +# CASAN DevKit (Level 2 — Adoption Kit) + +Everything a new project needs to adopt the CASAN governance harness in a repeatable way. +**Level 2 = Level 1 core harness + adoption tooling.** + +## Contents +| Path | Purpose | +|---|---| +| `install.sh` | Install core harness + `bin/casan` into a target repo, scaffold a domain, register it | +| `Dockerfile.harness` | Minimal image to run the gate on any mounted repo (`casan gate`) | +| `templates/domain-pack/` | Per-project domain scaffold (input / golden-runs / corpus / `domain-pack.yaml`) | +| `templates/gitea-workflow/ci.yml` | Reusable Gitea Actions gate workflow | +| `templates/project/` | Minimal new-project skeleton that consumes the harness | + +## Quick adopt +```bash +# from a CASAN source hub or an extracted casan-devkit bundle +packages/casan-devkit/install.sh --target ../my-project --project ticketing --domain "Ticketing" +cd ../my-project +# add requirement + golden baseline under apps/ticketing/domain/, then: +CASAN_DOMAIN_ROOT=apps/ticketing/domain bin/casan gate +bin/casan reuse # HARNESS_REUSE_VALID +``` + +## Guides +- `docs/packaging/ADOPTION_GUIDE.md` — end-to-end adoption +- `docs/packaging/DOMAIN_PACK_GUIDE.md` — how to fill a domain pack +- `docs/packaging/CI_GUIDE.md` — wire the gate into Gitea CI +- `docs/packaging/DOCKER_GUIDE.md` — run/build the harness image + +Adoption is **config + domain only** — you never edit gate logic (H1→H7). diff --git a/packages/casan-devkit/install.sh b/packages/casan-devkit/install.sh new file mode 100755 index 0000000..54dcac2 --- /dev/null +++ b/packages/casan-devkit/install.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# CASAN DevKit installer — adopt the CASAN harness into a target project. +# +# Copies the Level-1 core harness (packages/casan-harness + bin/casan) into a target +# repo, scaffolds a per-project domain from the domain-pack template, and registers the +# project in project-registry.json so `casan reuse` sees it. Does NOT touch harness gate +# logic — adoption is config + domain only. +# +# Usage: +# packages/casan-devkit/install.sh --target --project [--domain ] +# +# Run from a CASAN source hub (or an extracted casan-devkit bundle). +set -euo pipefail + +TARGET="" PROJECT="" DOMAIN="custom" +while [[ $# -gt 0 ]]; do + case "$1" in + --target) TARGET="$2"; shift 2 ;; + --project) PROJECT="$2"; shift 2 ;; + --domain) DOMAIN="$2"; shift 2 ;; + -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "install: unknown arg $1" >&2; exit 64 ;; + esac +done +[[ -n "$TARGET" && -n "$PROJECT" ]] || { echo "install: --target and --project are required" >&2; exit 64; } + +SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # source-hub / bundle root +[[ -d "$SRC/packages/casan-harness" ]] || { echo "install: cannot find packages/casan-harness under $SRC" >&2; exit 1; } + +echo "==> installing CASAN core into $TARGET (project=$PROJECT domain=$DOMAIN)" +mkdir -p "$TARGET/packages" "$TARGET/bin" "$TARGET/apps/$PROJECT/domain" + +# 1) core harness + CLI +rsync -a --exclude='__pycache__' --exclude='*.pyc' "$SRC/packages/casan-harness/" "$TARGET/packages/casan-harness/" +cp "$SRC/bin/casan" "$TARGET/bin/casan"; chmod +x "$TARGET/bin/casan" +[[ -f "$SRC/VERSION" ]] && cp "$SRC/VERSION" "$TARGET/VERSION" + +# 2) per-project domain from the domain-pack template +rsync -a "$SRC/packages/casan-devkit/templates/domain-pack/" "$TARGET/apps/$PROJECT/domain/" + +# 3) Gitea CI workflow (adoption) +mkdir -p "$TARGET/.gitea/workflows" +cp "$SRC/packages/casan-devkit/templates/gitea-workflow/ci.yml" "$TARGET/.gitea/workflows/casan-ci.yml" + +# 4) register in project-registry.json (append if absent) +REG="$TARGET/packages/casan-harness/level5/project-registry.json" +python3 - "$REG" "$PROJECT" "$DOMAIN" <<'PY' +import json, sys +reg, pid, dom = sys.argv[1], sys.argv[2], sys.argv[3] +data = json.load(open(reg)) +if not any(p.get("project_id") == pid for p in data["projects"]): + data["projects"].append({ + "project_id": pid, "domain": dom, "domain_root": f"apps/{pid}/domain", + "harness_package": "fpt-casan-sdd-harness", + "harness_version": data["projects"][0]["harness_version"], + "status": "active", + }) + json.dump(data, open(reg, "w"), indent=2, ensure_ascii=False); open(reg, "a").write("\n") + print(f"registered {pid}") +else: + print(f"{pid} already registered") +PY + +cat < done. Next steps in $TARGET: + 1. Put your requirement in apps/$PROJECT/domain/input/requirement.md + 2. Add golden baseline in apps/$PROJECT/domain/golden-runs/ + 3. Run the gate: CASAN_DOMAIN_ROOT=apps/$PROJECT/domain bin/casan gate + 4. Prove reuse: bin/casan reuse # expects HARNESS_REUSE_VALID +See docs/packaging/ADOPTION_GUIDE.md and DOMAIN_PACK_GUIDE.md. +EOF diff --git a/packages/casan-devkit/templates/domain-pack/corpus/benign-corpus/README.md b/packages/casan-devkit/templates/domain-pack/corpus/benign-corpus/README.md new file mode 100644 index 0000000..15afe61 --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/corpus/benign-corpus/README.md @@ -0,0 +1 @@ +# Benign corpus for H4 false-positive budget. One .txt per benign sample. diff --git a/packages/casan-devkit/templates/domain-pack/corpus/redteam-corpus.example.jsonl b/packages/casan-devkit/templates/domain-pack/corpus/redteam-corpus.example.jsonl new file mode 100644 index 0000000..4649634 --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/corpus/redteam-corpus.example.jsonl @@ -0,0 +1 @@ +{"note":"red-team attack vectors for this domain — one JSON object per line"} diff --git a/packages/casan-devkit/templates/domain-pack/corpus/redteam-vectors.example.jsonl b/packages/casan-devkit/templates/domain-pack/corpus/redteam-vectors.example.jsonl new file mode 100644 index 0000000..64d8b45 --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/corpus/redteam-vectors.example.jsonl @@ -0,0 +1 @@ +{"note":"red-team vectors used by benign-fp-report / redteam metrics"} diff --git a/packages/casan-devkit/templates/domain-pack/domain-pack.yaml b/packages/casan-devkit/templates/domain-pack/domain-pack.yaml new file mode 100644 index 0000000..00dd428 --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/domain-pack.yaml @@ -0,0 +1,33 @@ +# CASAN Domain Pack — declarative domain adoption (template). +# Copy this into apps//domain/ and fill it in. The harness reads domain data +# from CASAN_DOMAIN_ROOT (defaults to apps//domain); this file documents what +# each project must provide. See docs/packaging/DOMAIN_PACK_GUIDE.md. + +domain: + id: custom # short id, e.g. okr, ticketing, inventory + name: "Custom domain" + owner: your-team + +# Requirement/architecture the harness pipeline consumes (H3/traceability, source-gen). +input: + requirement: input/requirement.md # FR-xx table drives traceability + architecture: input/architecture.md # optional tech-stack/context + +# Golden baselines for drift detection (H7). similarity=1.0 vs golden ⇒ no drift. +golden_runs: + dir: golden-runs + # - artifact: plan -> golden-runs/plan.golden.txt + +# Red-team + benign corpus for H4 security scoring (attack recall + FP budget). +corpus: + redteam: corpus/redteam-corpus.jsonl # attack vectors (per-line JSON) + redteam_vectors: corpus/redteam-vectors.jsonl + benign: corpus/benign-corpus # dir of benign .txt (false-positive budget) + +# Requirement→code→test map for the traceability gate (Plan-10). +traceability_map: traceability-map.json + +# Optional per-domain threshold overrides (else harness defaults apply). +thresholds: + # drift_min_similarity: 0.85 + # fp_rate_max: 0.05 diff --git a/packages/casan-devkit/templates/domain-pack/golden-runs/README.md b/packages/casan-devkit/templates/domain-pack/golden-runs/README.md new file mode 100644 index 0000000..a863fc6 --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/golden-runs/README.md @@ -0,0 +1 @@ +# Golden baselines for drift-detect (H7). Add .golden.txt here. diff --git a/packages/casan-devkit/templates/domain-pack/input/requirement.md b/packages/casan-devkit/templates/domain-pack/input/requirement.md new file mode 100644 index 0000000..3a23e7b --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/input/requirement.md @@ -0,0 +1,17 @@ +# Requirement (template) + +> Replace this with your domain's requirements. The **FR-xx table below drives the +> traceability gate** (Plan-10): every `FR-xx` must map to ≥1 code file + ≥1 test in +> `traceability-map.json`. Keep the `| FR-xx | ... |` table format. + +## Functional Requirements + +| ID | Requirement | +|------|-------------| +| FR-01 | Example: user can log in and receive a session token | +| FR-02 | Example: user can create a primary domain entity | +| FR-03 | Example: user can update entity progress | + +## Notes +- Add use cases, constraints, and UI expectations as normal prose below. +- Secrets/credentials must NOT appear here (H4 input scan will block them). diff --git a/packages/casan-devkit/templates/domain-pack/traceability-map.example.json b/packages/casan-devkit/templates/domain-pack/traceability-map.example.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/packages/casan-devkit/templates/domain-pack/traceability-map.example.json @@ -0,0 +1 @@ +{} diff --git a/packages/casan-devkit/templates/gitea-workflow/ci.yml b/packages/casan-devkit/templates/gitea-workflow/ci.yml new file mode 100644 index 0000000..f352920 --- /dev/null +++ b/packages/casan-devkit/templates/gitea-workflow/ci.yml @@ -0,0 +1,39 @@ +# CASAN harness gate — Gitea Actions workflow (adoption template). +# Copy to .gitea/workflows/casan-ci.yml in your project. Assumes the CASAN core harness +# lives at packages/casan-harness/ (via casan-devkit install.sh) and domain data at +# apps//domain/. Runs the full governance gate on every push/PR. +name: CASAN Gate + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + casan-gate: + runs-on: ci-runner + timeout-minutes: 45 + env: + CASAN_CI_RUN_FRONTEND: "0" # set 1 if your project has a frontend workspace + CASAN_CI_RUN_BACKEND: "0" # set 1 if your project has backend tests + CASAN_CI_RUN_INFRA_LAB: "0" + CASAN_CI_STEP_TIMEOUT_SEC: "1200" # headroom; some suites are model-backed + # CASAN_DOMAIN_ROOT: apps//domain # uncomment + set for your project + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Ensure toolchain + run: | + set -euo pipefail + command -v python3 >/dev/null || { apt-get update && apt-get install -y python3; } + python3 --version + + - name: Run CASAN harness gate + run: bash packages/casan-harness/scripts/bash/ci-harness-gate.sh + + - name: Verify audit chain + policy bundle + run: | + bash packages/casan-harness/scripts/bash/verify-audit-chain.sh + bash packages/casan-harness/scripts/bash/sign-policy-bundle.sh verify diff --git a/packages/casan-devkit/templates/project/.gitea/workflows/casan-ci.yml b/packages/casan-devkit/templates/project/.gitea/workflows/casan-ci.yml new file mode 100644 index 0000000..f352920 --- /dev/null +++ b/packages/casan-devkit/templates/project/.gitea/workflows/casan-ci.yml @@ -0,0 +1,39 @@ +# CASAN harness gate — Gitea Actions workflow (adoption template). +# Copy to .gitea/workflows/casan-ci.yml in your project. Assumes the CASAN core harness +# lives at packages/casan-harness/ (via casan-devkit install.sh) and domain data at +# apps//domain/. Runs the full governance gate on every push/PR. +name: CASAN Gate + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + casan-gate: + runs-on: ci-runner + timeout-minutes: 45 + env: + CASAN_CI_RUN_FRONTEND: "0" # set 1 if your project has a frontend workspace + CASAN_CI_RUN_BACKEND: "0" # set 1 if your project has backend tests + CASAN_CI_RUN_INFRA_LAB: "0" + CASAN_CI_STEP_TIMEOUT_SEC: "1200" # headroom; some suites are model-backed + # CASAN_DOMAIN_ROOT: apps//domain # uncomment + set for your project + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Ensure toolchain + run: | + set -euo pipefail + command -v python3 >/dev/null || { apt-get update && apt-get install -y python3; } + python3 --version + + - name: Run CASAN harness gate + run: bash packages/casan-harness/scripts/bash/ci-harness-gate.sh + + - name: Verify audit chain + policy bundle + run: | + bash packages/casan-harness/scripts/bash/verify-audit-chain.sh + bash packages/casan-harness/scripts/bash/sign-policy-bundle.sh verify diff --git a/packages/casan-devkit/templates/project/README.md b/packages/casan-devkit/templates/project/README.md new file mode 100644 index 0000000..369089e --- /dev/null +++ b/packages/casan-devkit/templates/project/README.md @@ -0,0 +1,21 @@ +# — CASAN-governed project (scaffold) + +Generated by `casan-devkit install.sh`. Layout: + +``` +packages/casan-harness/ # Level-1 core harness (installed; do not edit gate logic) +bin/casan # CLI wrapper +apps//domain/ # YOUR domain pack (input / golden-runs / corpus / domain-pack.yaml) +.gitea/workflows/casan-ci.yml # gate on push/PR +.specify/ # runtime state (created on first run: logs, audit, governance) +``` + +## Run +```bash +CASAN_DOMAIN_ROOT=apps//domain bin/casan gate # full governance gate +bin/casan run in.txt out.txt my_step -- # one governed step +bin/casan reuse # HARNESS_REUSE_VALID +``` + +Fill `apps//domain/` first (see DOMAIN_PACK_GUIDE.md). Upgrade the harness by +re-running install.sh with a newer casan-devkit — your domain data is untouched. diff --git a/packages/casan-enterprise/README.md b/packages/casan-enterprise/README.md new file mode 100644 index 0000000..83bb0ff --- /dev/null +++ b/packages/casan-enterprise/README.md @@ -0,0 +1,34 @@ +# CASAN Enterprise / Governed Agent Console (Level 4) · **FUTURE / structure-only** + +> Status: **FUTURE.** Documented + scaffolded, NOT implemented in this task. +> `package-release.sh enterprise` **refuses to build** (no fake-complete package) — several +> building blocks already exist inside the core harness, but the governed console itself +> does not. Do not ship this as a product yet. + +Enterprise governed AI-SDLC console. Packages: `casan-enterprise`, `casan-governed-console`. + +## Building blocks that ALREADY exist (in core harness, reusable) +| Capability | Where | +|---|---| +| RBAC | `packages/casan-harness/scripts/bash/rbac-check.py` | +| Tenant isolation | `tenant-store.sh`, `tenant-paths.sh`, `tenant-registry-verify.sh`, `tenant-crypt.sh` | +| Approval workflow | `approval-verify.sh`, `approval-sign.sh`, `approval-jwt-mint.py` | +| KMS | `vault-kms.sh` | +| WORM ledger | `worm-ledger.py` | +| Kill-switch / quota | `kill-switch.sh`, `cost-spike-detect.sh` | +| Loop governance | Plan-17 loop primitives (`loop-*.py`) | + +## Components still to build (NOT in this task) +- Governed Chat Console (Plan-18) · Prompt Mode Router · Model Provider Management +- Operator mode · Codegen mode · Agent/Skill Registry · policy-versioning UI + +## Why it refuses to package +Per the packaging principle, a level that isn't implemented must **fail clearly** rather +than emit a fake-complete artifact. Enterprise is `status: future` in +`packaging/levels.json`, so `scripts/package-release.sh enterprise` exits non-zero with an +explanation. When the console is built, flip its status to `preview`/`implemented`. + +## To implement later +Sequence: Plan-14 (RBAC console) → Plan-13 (Control Plane) → Plan-18 (Governed Chat +Console: read-only → operator → chat-as-loop → multi-tenant). Reuse the existing blocks +above instead of re-writing them. diff --git a/packages/casan-platform/README.md b/packages/casan-platform/README.md new file mode 100644 index 0000000..7e7f8cb --- /dev/null +++ b/packages/casan-platform/README.md @@ -0,0 +1,30 @@ +# CASAN Platform (Level 3 — Productization UI) · **PREVIEW / structure-only** + +> Status: **PREVIEW.** Only the AgentOps **dashboard** exists today (shipped inside the core +> harness). The rest of the platform UI is scaffolded here as structure + intent — NOT +> implemented in this task. `package-release.sh platform` builds a clearly-stamped +> `casan-platform-preview-*` bundle containing only what exists. + +Optional layer for teams that want UI / dashboard / visibility. Packages: `casan-platform`, +`casan-control-panel`. + +## Components +| Component | Status | Where | +|---|---|---| +| AgentOps Dashboard | ✅ exists | `packages/casan-harness/scripts/bash/dashboard-server.py` + `dashboard-serve.sh` (`casan dashboard`) | +| Control Panel | 📋 planned | Plan-13 (`docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md`) | +| Evidence Pack Viewer | 📋 planned | reads `docs/output/casan/evidence-packs/` | +| Attack Battery Viewer | 📋 planned | reads red-team corpus + H4 recall results | +| Run History Viewer | 📋 planned | reads `.specify/logs/level5/pipeline-run.jsonl` | +| Read-only Ask CASAN | 📋 planned | Plan-18 MVP-0 (read-only) | +| Gitea webhook integration | 📋 planned | trigger gate / publish evidence on push | + +## Build (preview) +```bash +scripts/package-release.sh platform # → dist/casan-platform-preview-vX.Y.Z.tar.gz +``` +The bundle includes a `PREVIEW-INCOMPLETE.txt` marker. Do not treat it as a finished product. + +## To implement later +Start from Plan-13 (Control Plane) + Plan-18 MVP-0 (Ask CASAN read-only). Keep the UI +**read-only over harness artifacts** first; write/governed actions belong to Level 4. diff --git a/packaging/levels.json b/packaging/levels.json new file mode 100644 index 0000000..169e075 --- /dev/null +++ b/packaging/levels.json @@ -0,0 +1,94 @@ +{ + "casan_packaging": "level-based source hub", + "version": "1.0.0", + "note": "Single source of truth for what each release bundle contains and its maturity. The repo is a source hub holding all levels; releases are SPLIT so downstream adopts only the level it needs. See docs/packaging/CASAN_PACKAGING_PLAN.md.", + "levels": { + "core": { + "level": 1, + "package_names": ["casan-core", "casan-harness"], + "status": "implemented", + "summary": "H1-H7 harness core, security/action gates, evidence pack, audit, cost/telemetry, hardening tests, policy/config defaults, lightweight CLI.", + "target_users": ["governance-harness-only projects", "BJT initial adoption", "CI gate usage"], + "includes": [ + "packages/casan-harness/scripts", + "packages/casan-harness/security", + "packages/casan-harness/governance", + "packages/casan-harness/agentops", + "packages/casan-harness/config", + "packages/casan-harness/level5", + "packages/casan-harness/memory", + "packages/casan-harness/templates", + "packages/casan-harness/tests", + "packages/casan-harness/init-options.json", + "packages/casan-harness/README.md", + "bin/casan", + "VERSION" + ], + "excludes_globs": ["**/__pycache__/**", "**/*.pyc", "**/.DS_Store"], + "artifact": "casan-core-v{VERSION}.tar.gz", + "docker_image": "casan-harness:{VERSION}" + }, + "devkit": { + "level": 2, + "package_names": ["casan-devkit", "casan-project-kit"], + "status": "implemented", + "summary": "Level 1 + project templates, domain-pack scaffold, Gitea workflow templates, harness Dockerfile, install script, adoption/CI/domain-pack guides.", + "target_users": ["new projects adopting CASAN", "internal teams needing a repeatable setup"], + "extends": "core", + "includes": [ + "packages/casan-devkit", + "docs/packaging/ADOPTION_GUIDE.md", + "docs/packaging/CI_GUIDE.md", + "docs/packaging/DOMAIN_PACK_GUIDE.md", + "docs/packaging/DOCKER_GUIDE.md", + "docs/packaging/CASAN_PACKAGING_PLAN.md", + "scripts/package-release.sh" + ], + "artifact": "casan-devkit-v{VERSION}.tar.gz" + }, + "platform": { + "level": 3, + "package_names": ["casan-platform", "casan-control-panel"], + "status": "preview", + "summary": "Optional productization UI: Control Panel, Dashboard, Evidence/Attack/Run-History viewers, read-only Ask CASAN, Gitea webhook. Only the AgentOps dashboard exists today; the rest is structure-only.", + "target_users": ["teams wanting UI/dashboard/visibility", "demo/training/productization"], + "extends": "devkit", + "implemented_components": ["agentops-dashboard (packages/casan-harness/scripts/bash/dashboard-server.py + dashboard-serve.sh)"], + "pending_components": ["control-panel", "evidence-pack-viewer", "attack-battery-viewer", "run-history-viewer", "read-only Ask CASAN", "gitea-webhook-integration"], + "includes": [ + "packages/casan-platform", + "packages/casan-harness/scripts/bash/dashboard-server.py", + "packages/casan-harness/scripts/bash/dashboard-serve.sh" + ], + "artifact": "casan-platform-preview-v{VERSION}.tar.gz", + "docker_image": "casan-platform:{VERSION} (preview)" + }, + "enterprise": { + "level": 4, + "package_names": ["casan-enterprise", "casan-governed-console"], + "status": "future", + "summary": "Governed Chat Console, Prompt Mode Router, Model Provider Management, Operator/Codegen modes, Agent/Skill Registry, RBAC, approval workflow, tenant isolation, KMS/WORM, policy versioning.", + "target_users": ["enterprise/internal platform", "multi-project governance", "official governed AI-SDLC console"], + "extends": "platform", + "implemented_components": [ + "RBAC (rbac-check.py)", + "tenant isolation (tenant-store.sh/tenant-paths.sh/tenant-registry-verify.sh/tenant-crypt.sh)", + "approval workflow (approval-verify.sh/approval-sign.sh/approval-jwt-mint.py)", + "KMS (vault-kms.sh)", + "WORM (worm-ledger.py)", + "kill-switch (kill-switch.sh)" + ], + "pending_components": ["governed-chat-console", "prompt-mode-router", "model-provider-management", "operator-mode", "codegen-mode", "agent-skill-registry", "policy-versioning-ui"], + "includes": ["packages/casan-enterprise"], + "artifact": "casan-enterprise-preview-v{VERSION}.tar.gz", + "docker_image": "casan-enterprise:{VERSION} (future)" + } + }, + "bundles": { + "core": {"builds": ["core"], "status": "implemented"}, + "devkit": {"builds": ["core", "devkit"], "status": "implemented"}, + "platform": {"builds": ["core", "devkit", "platform"], "status": "preview"}, + "all-in-one-demo": {"builds": ["core", "devkit", "platform", "apps/okr/domain", "backend", "frontend", "docs/output/casan", "package.json", "package-lock.json", "Dockerfile.backend", "Dockerfile.frontend", "docker-compose.prod.yml"], "status": "implemented", "note": "Full runnable snapshot: harness + OKR app (backend/frontend) + OKR demo domain + latest evidence, for demo/training. The traceability gate needs the app source, so it is bundled here."}, + "enterprise": {"builds": ["enterprise"], "status": "future", "note": "Refused by package-release.sh — Level 4 is structure-only; no fake-complete artifact."} + } +} diff --git a/scripts/package-release.sh b/scripts/package-release.sh new file mode 100755 index 0000000..273933d --- /dev/null +++ b/scripts/package-release.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# CASAN level-based release packager. +# +# scripts/package-release.sh core # Level 1 — always buildable +# scripts/package-release.sh devkit # Level 2 — always buildable +# scripts/package-release.sh platform # Level 3 — PREVIEW (dashboard only today) +# scripts/package-release.sh all-in-one-demo # everything runnable + OKR demo domain +# scripts/package-release.sh enterprise # Level 4 — FUTURE → fails clearly +# +# Contents + maturity are driven by packaging/levels.json (single source of truth). +# A bundle whose level is `future` is REFUSED (no fake-complete package). A `preview` +# bundle is built but clearly stamped PREVIEW/INCOMPLETE and named *-preview. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +MANIFEST="packaging/levels.json" +VERSION="$(cat VERSION 2>/dev/null || echo 0.0.0)" +DIST="$ROOT/dist" + +BUNDLE="${1:-}" +if [[ -z "$BUNDLE" ]]; then + echo "Usage: package-release.sh " >&2 + exit 64 +fi +[[ -f "$MANIFEST" ]] || { echo "package-release: missing $MANIFEST" >&2; exit 1; } + +# --- read bundle definition from the manifest via python3 --------------------- +read -r BUNDLE_STATUS BUILDS < <(python3 - "$MANIFEST" "$BUNDLE" <<'PY' +import json, sys +m = json.load(open(sys.argv[1])); b = sys.argv[2] +bundles = m["bundles"] +if b not in bundles: + print("UNKNOWN -"); sys.exit(0) +spec = bundles[b] +print(spec.get("status", "unknown"), ",".join(spec.get("builds", []))) +PY +) + +if [[ "$BUNDLE_STATUS" == "UNKNOWN" ]]; then + echo "package-release: unknown bundle '$BUNDLE'. Valid: core devkit platform all-in-one-demo enterprise" >&2 + exit 64 +fi + +# --- refuse to build a fake-complete package for a not-implemented level ------- +if [[ "$BUNDLE_STATUS" == "future" ]]; then + cat >&2 < + python3 - "$MANIFEST" "$1" <<'PY' +import json, sys +m = json.load(open(sys.argv[1])); lvl = sys.argv[2] +# a "level" here is a levels.* key; the demo pseudo-paths are handled by the caller +node = m["levels"].get(lvl) +if node: + for inc in node.get("includes", []): + print(inc) +PY +} + +STAGE="$(mktemp -d)" +PKGDIR="$STAGE/casan-$BUNDLE$SUFFIX-v$VERSION" +mkdir -p "$PKGDIR" +trap 'rm -rf "$STAGE"' EXIT + +copy_path() { # + local src="$ROOT/$1" + if [[ ! -e "$src" ]]; then + echo " ! skip (missing): $1" >&2 + return 0 + fi + local dest="$PKGDIR/$1" + mkdir -p "$(dirname "$dest")" + # copy, dropping caches/artifacts + if [[ -d "$src" ]]; then + rsync -a --exclude='__pycache__' --exclude='*.pyc' --exclude='.DS_Store' "$src/" "$dest/" 2>/dev/null \ + || cp -R "$src/" "$dest/" + else + cp "$src" "$dest" + fi +} + +echo "==> building bundle '$BUNDLE' (status=$BUNDLE_STATUS) version=$VERSION" +SEEN=" " # space-delimited dedup (bash 3.2 compatible — no associative arrays) +seen() { case "$SEEN" in *" $1 "*) return 0;; *) SEEN="$SEEN$1 "; return 1;; esac; } +IFS=',' read -ra LEVELS <<< "$BUILDS" +for lvl in "${LEVELS[@]}"; do + # Entries that are level keys (core/devkit/platform/enterprise) expand via the manifest; + # anything else is a literal path (demo snapshot: backend, frontend, apps/okr/domain, ...). + case "$lvl" in + core|devkit|platform|enterprise) ;; + *) seen "$lvl" && continue + echo " + $lvl"; copy_path "$lvl"; continue ;; + esac + echo " level: $lvl" + while IFS= read -r inc; do + [[ -z "$inc" ]] && continue + seen "$inc" && continue + echo " + $inc"; copy_path "$inc" + done < <(gather_includes "$lvl") +done + +# --- bundle manifest + preview notice ---------------------------------------- +{ + echo "CASAN release bundle" + echo "bundle: $BUNDLE" + echo "version: $VERSION" + echo "status: $BUNDLE_STATUS" + echo "built: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "builds: $BUILDS" +} > "$PKGDIR/BUNDLE-MANIFEST.txt" + +if [[ "$PREVIEW" == 1 ]]; then + cat > "$PKGDIR/PREVIEW-INCOMPLETE.txt" < levels.$BUNDLE.implemented_components). Pending components +are listed under pending_components. Do not treat this as a finished product. +EOF +fi + +# --- checksums + tar ---------------------------------------------------------- +( cd "$PKGDIR" && find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 shasum -a 256 > SHA256SUMS 2>/dev/null || true ) +mkdir -p "$DIST" +ARTIFACT="casan-$BUNDLE$SUFFIX-v$VERSION.tar.gz" +tar -czf "$DIST/$ARTIFACT" -C "$STAGE" "casan-$BUNDLE$SUFFIX-v$VERSION" +SIZE="$(du -h "$DIST/$ARTIFACT" | cut -f1)" +( cd "$DIST" && shasum -a 256 "$ARTIFACT" > "$ARTIFACT.sha256" ) + +echo "==> BUILT $DIST/$ARTIFACT ($SIZE)" +[[ "$PREVIEW" == 1 ]] && echo " (PREVIEW — incomplete; see PREVIEW-INCOMPLETE.txt inside)" +echo " sha256: $(cut -d' ' -f1 "$DIST/$ARTIFACT.sha256")"