Files
CASAN/scripts/package-release.sh
T
thanhnvandClaude Opus 4.8 8c20cfde9f feat(packaging): level-based source hub — Core + DevKit packaging, Platform/Enterprise scaffold
Organize CASAN as a reusable source hub with SPLIT releases so downstream adopts only the
level it needs (packaging/levels.json is the single source of truth).

Implemented now:
- Level 1 Core: bin/casan CLI (run/gate/test/verify/reuse/dashboard) + VERSION.
- Level 2 DevKit: packages/casan-devkit (install.sh, Dockerfile.harness, templates:
  project scaffold, domain-pack, gitea-workflow).
- scripts/package-release.sh core|devkit|platform|all-in-one-demo — builds split bundles
  into dist/ (BUNDLE-MANIFEST + SHA256SUMS); platform is stamped PREVIEW/INCOMPLETE;
  enterprise (future) is REFUSED (exit 3, no fake-complete package). Bundles verified:
  extract → bin/casan works, deterministic + domain suites pass, casan reuse VALID.
- docs/packaging: CASAN_PACKAGING_PLAN + ADOPTION + CI + DOMAIN_PACK + GITEA_PACKAGE + DOCKER.

Structure + docs only:
- Level 3 packages/casan-platform (dashboard exists; control-panel/viewers pending).
- Level 4 packages/casan-enterprise (RBAC/tenant/KMS/WORM/approval exist in core; governed
  console pending). No Chat Console/RBAC-console/tenant-console/model-mgmt built in this task.

Harness change (enables extracted bundles to self-resolve): casan-paths.sh + the Python
project_root() walk-ups now accept a second root marker `packages/casan-harness` in addition
to `.specify`, so a freshly-unpacked core/devkit/demo bundle (no `.specify` yet) roots
correctly and creates state on first run. In an adopted repo `.specify` still matches first.
policy-bundle.yaml paths corrected to packages/casan-harness (re-signed). Full gate 64/0/3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:55:37 +09:00

150 lines
5.6 KiB
Bash
Executable File

#!/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 <core|devkit|platform|all-in-one-demo|enterprise>" >&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 <<EOF
package-release: '$BUNDLE' is a FUTURE / structure-only level — refusing to build.
Level 4 (enterprise / governed console) is documented + scaffolded but NOT implemented
in this task. Some building blocks already exist inside core (RBAC, tenant isolation,
approval workflow, KMS, WORM) — see packages/casan-enterprise/README.md — but the
Governed Chat Console / Prompt Router / Model-Provider Management do not exist yet, so
no 'complete' enterprise artifact can be produced. This is intentional (no fake-complete
packages). Build 'core', 'devkit', 'platform', or 'all-in-one-demo' instead.
EOF
exit 3
fi
PREVIEW=0
SUFFIX=""
[[ "$BUNDLE_STATUS" == "preview" ]] && { PREVIEW=1; SUFFIX="-preview"; }
# --- gather include globs for the levels this bundle builds -------------------
gather_includes() { # <level>
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() { # <relpath>
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" <<EOF
⚠️ PREVIEW / INCOMPLETE BUNDLE — NOT production-complete.
This '$BUNDLE' bundle is a PREVIEW. Only the components that exist today are included
(see packaging/levels.json -> 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")"