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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4918012199
commit
8c20cfde9f
Executable
+149
@@ -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 <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")"
|
||||
Reference in New Issue
Block a user