From d30a4cce00416b821f71e09d5369c07b15739207 Mon Sep 17 00:00:00 2001 From: code-be-02 Date: Tue, 25 Aug 2026 12:55:40 +0000 Subject: [PATCH] feat(git-tools): consume .mosaic/repo.json declarations in compat mode (T51 WP5b) (#1416) Co-authored-by: code-be-02 --- packages/mosaic/framework/defaults/TOOLS.md | 1 + .../framework/tools/git/ci-queue-wait.sh | 22 ++ .../framework/tools/git/mosaic-worktree.sh | 41 +++ .../mosaic/framework/tools/git/pr-create.sh | 54 ++- .../mosaic/framework/tools/git/pr-merge.sh | 41 ++- .../mosaic/framework/tools/git/repo-decl.sh | 198 +++++++++++ .../tools/git/test-repo-decl-consumption.sh | 333 ++++++++++++++++++ packages/mosaic/package.json | 2 +- 8 files changed, 684 insertions(+), 8 deletions(-) create mode 100755 packages/mosaic/framework/tools/git/repo-decl.sh create mode 100755 packages/mosaic/framework/tools/git/test-repo-decl-consumption.sh diff --git a/packages/mosaic/framework/defaults/TOOLS.md b/packages/mosaic/framework/defaults/TOOLS.md index 9fd729c5..77e775ea 100644 --- a/packages/mosaic/framework/defaults/TOOLS.md +++ b/packages/mosaic/framework/defaults/TOOLS.md @@ -23,6 +23,7 @@ absent. Do not use raw `tmux send-keys` for fleet messaging. ```bash tools/git/pr-create.sh ... tools/git/issue-create.sh ... tools/git/pr-merge.sh ... tools/git/ci-queue-wait.sh --purpose push|merge # REQUIRED before any push/merge +tools/git/repo-decl.sh # shared .mosaic/repo.json consumption lib (sourced) ``` **Reviewer grants** — `tools/git/grant-reviewer.sh -u [-r /] [-t ]` adds a diff --git a/packages/mosaic/framework/tools/git/ci-queue-wait.sh b/packages/mosaic/framework/tools/git/ci-queue-wait.sh index 99556511..07f3b87b 100755 --- a/packages/mosaic/framework/tools/git/ci-queue-wait.sh +++ b/packages/mosaic/framework/tools/git/ci-queue-wait.sh @@ -503,6 +503,28 @@ if [[ -z "$BRANCH" ]]; then fi fi +# T51 WP5b (spec 4.1, review ruling C4/F8): the declaration adds ROUTE CONTEXT only. +# Branch-selection semantics are UNCHANGED — the guard keeps inspecting the +# exact head above. No declaration dependency gates the wait (4.3/DR2 R9: +# blocking here adds a blocker with no safety gain); absence is silent. +# shellcheck source=packages/mosaic/framework/tools/git/ci-queue-wait.sh +if git rev-parse --show-toplevel >/dev/null 2>&1 \ + && [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/repo-decl.sh" ]; then + source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/repo-decl.sh" + repo_decl_load + if [[ "$DECL_STATE" == invalid ]]; then + repo_decl_report_invalid + elif [[ "$DECL_STATE" == valid && "$DECL_SCHEMA" == 2 ]]; then + route="feature" + if [[ "$BRANCH" == "$DECL_TRUNK" ]]; then + route="trunk (integration head)" + elif [[ "$BRANCH" == "$DECL_RELEASE" ]]; then + route="release branch" + fi + echo "repo-decl: route context flow=$DECL_FLOW trunk=$DECL_TRUNK release=$DECL_RELEASE; guarded head '$BRANCH' is a $route head (spec 4.1)" >&2 + fi +fi + if [[ "$PLATFORM" == "github" ]]; then if ! command -v gh >/dev/null 2>&1; then record_cannot_assert "github-cli-unavailable" diff --git a/packages/mosaic/framework/tools/git/mosaic-worktree.sh b/packages/mosaic/framework/tools/git/mosaic-worktree.sh index 926365eb..ef8ff4db 100755 --- a/packages/mosaic/framework/tools/git/mosaic-worktree.sh +++ b/packages/mosaic/framework/tools/git/mosaic-worktree.sh @@ -192,6 +192,47 @@ cmd_new() { local path; path="$(derive_path "$branch")" assert_not_home "$path" + # T51 WP5b (spec 4.4 staged rule + 4.5 advisory policy): placement stays + # DERIVED; the declaration never moves the worktree. An INVALID declaration + # fails branch-creation loud (a broken structure file must not ride a new + # branch); an absent one warns (rollout window, Q-C); a valid one contributes + # policy ADVICE only. The advisory worktree_root comparison runs only when + # MOSAIC_HOST_ROOT is set (1.2a: warn-and-omit for advisory display). + # shellcheck source=packages/mosaic/framework/tools/git/repo-decl.sh + _rd="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)/repo-decl.sh" +if [ -f ""$_rd"" ]; then + source ""$_rd"" + repo_decl_load +else + DECL_STATE=absent; DECL_SCHEMA="" + repo_decl_warn() { printf 'repo-decl: %s\n' "$*" >&2; } + repo_decl_report_invalid() { :; } + repo_decl_warn_absent_reversible() { :; } + repo_decl_warn_absent_irreversible() { :; } + repo_decl_remote_matches() { return 0; } + repo_decl_check_transition() { return 2; } +fi + case "$DECL_STATE" in + invalid) + repo_decl_report_invalid + die "worktree branch-creation refused: the structure declaration is invalid (spec 4.4 — fix it before creating branches)" + ;; + absent) + repo_decl_warn_absent_irreversible "worktree branch-creation" + ;; + valid) + if [ "$DECL_SCHEMA" = 2 ] && [ "$DECL_POLICY" = "orchestrator-precreated" ]; then + echo "repo-decl: worktree_policy=orchestrator-precreated (TRANSITIONAL, spec 4.5): tasking pre-creates worktrees; creating one directly is the interim path until the J3/#1174 amendment unblocks the wrapper consumer." >&2 + fi + if [ -n "${MOSAIC_HOST_ROOT:-}" ] && [ -n "$DECL_WT_ROOT" ]; then + dwt="$(repo_decl_path "$DECL_WT_ROOT" 2>/dev/null || true)" + if [ -n "$dwt" ] && [ "${dwt%/}" != "${WT_ROOT%/}" ]; then + echo "repo-decl: derived root $WT_ROOT diverges from the declared advisory worktree_root $dwt (advisory per spec 4.1/5.2 — placement stays derived)" >&2 + fi + fi + ;; + esac + if [ -e "$path" ]; then echo "exists: $path" echo "(already checked out — reuse it, or 'rm' it first)" diff --git a/packages/mosaic/framework/tools/git/pr-create.sh b/packages/mosaic/framework/tools/git/pr-create.sh index ea4524b7..31a8dfa9 100755 --- a/packages/mosaic/framework/tools/git/pr-create.sh +++ b/packages/mosaic/framework/tools/git/pr-create.sh @@ -78,8 +78,8 @@ gitea_pr_create_api() { # historical "main" literal, which mistargeted every fallback PR on # repos whose trunk is not main (e.g. mosaicstack/stack -> next). local api_base="" - if [[ -n "$BASE_BRANCH" ]]; then - api_base="$BASE_BRANCH" + if [[ -n "$EFFECTIVE_BASE" ]]; then + api_base="$EFFECTIVE_BASE" else api_base=$(gitea_default_branch) || { echo "Error: could not resolve the forge default branch for the API-fallback base; pass -B explicitly" >&2 @@ -198,6 +198,52 @@ if [[ -z "$HEAD_BRANCH" ]]; then HEAD_BRANCH=$(git branch --show-current) fi +# T51 WP5b: declaration-driven base resolution (spec 4.1). Precedence: +# explicit -B -> validated as an ALLOWED transition (4.2: a flag is input, +# not authority) when a consumable declaration exists +# declared trunk (v2 declarations only) -> used directly +# legacy -> WP5a forge-default floor (unmanaged/absent/v1, 4.3) +# shellcheck source=packages/mosaic/framework/tools/git/repo-decl.sh +if [ -f "$SCRIPT_DIR/repo-decl.sh" ]; then + source "$SCRIPT_DIR/repo-decl.sh" + repo_decl_load +else + DECL_STATE=absent; DECL_SCHEMA="" + repo_decl_warn() { printf 'repo-decl: %s\n' "$*" >&2; } + repo_decl_report_invalid() { :; } + repo_decl_warn_absent_reversible() { :; } + repo_decl_warn_absent_irreversible() { :; } + repo_decl_remote_matches() { return 0; } + repo_decl_check_transition() { return 2; } +fi +EFFECTIVE_BASE="$BASE_BRANCH" +case "$DECL_STATE" in + invalid) repo_decl_report_invalid ;; +esac +if [[ "$DECL_STATE" == valid && "$DECL_SCHEMA" != 2 ]]; then + repo_decl_warn "declaration is v$DECL_SCHEMA — carries no consumable flow/trunk fields; legacy behavior" +fi +if [[ "$DECL_STATE" == valid && "$DECL_SCHEMA" == 2 ]]; then + # Write path: a normalized-remote mismatch refuses (spec 5.3). + if ! repo_decl_remote_matches; then + echo "Error: origin remote does not match the declared canonical_remote (spec 5.3, write path) — refusing to create a PR against the wrong forge. Fix the origin remote or the declaration." >&2 + exit 1 + fi + if [[ -n "$BASE_BRANCH" ]]; then + trc=0 + repo_decl_check_transition "$HEAD_BRANCH" "$BASE_BRANCH" || trc=$? + if [[ "$trc" == 1 ]]; then + echo "Error: -B '$BASE_BRANCH' is not an allowed transition for head '$HEAD_BRANCH' under the declared flow (spec 4.2). The declaration governs; supply an allowed base." >&2 + exit 1 + fi + # trc 2 cannot happen here (state=valid): 0 = allowed + else + EFFECTIVE_BASE="$DECL_TRUNK" + fi +elif [[ -z "$BASE_BRANCH" ]]; then + repo_decl_warn_absent_reversible "pr-create" +fi + # Add issue reference to body if provided if [[ -n "$ISSUE" ]]; then if [[ -n "$BODY" ]]; then @@ -215,7 +261,7 @@ case "$PLATFORM" in github) CMD=(gh pr create --title "$TITLE") [[ -n "$BODY" ]] && CMD+=(--body "$BODY") - [[ -n "$BASE_BRANCH" ]] && CMD+=(--base "$BASE_BRANCH") + [[ -n "$EFFECTIVE_BASE" ]] && CMD+=(--base "$EFFECTIVE_BASE") [[ -n "$HEAD_BRANCH" ]] && CMD+=(--head "$HEAD_BRANCH") [[ -n "$LABELS" ]] && CMD+=(--label "$LABELS") [[ -n "$MILESTONE" ]] && CMD+=(--milestone "$MILESTONE") @@ -240,7 +286,7 @@ case "$PLATFORM" in REPO_ARGS=(--repo "$REPO_SLUG" --login "$GITEA_LOGIN_NAME") CMD=(tea pr create "${REPO_ARGS[@]}" --title "$TITLE") [[ -n "$BODY" ]] && CMD+=(--description "$BODY") - [[ -n "$BASE_BRANCH" ]] && CMD+=(--base "$BASE_BRANCH") + [[ -n "$EFFECTIVE_BASE" ]] && CMD+=(--base "$EFFECTIVE_BASE") [[ -n "$HEAD_BRANCH" ]] && CMD+=(--head "$HEAD_BRANCH") # Handle labels for tea diff --git a/packages/mosaic/framework/tools/git/pr-merge.sh b/packages/mosaic/framework/tools/git/pr-merge.sh index 33be9ee6..89d63bb0 100755 --- a/packages/mosaic/framework/tools/git/pr-merge.sh +++ b/packages/mosaic/framework/tools/git/pr-merge.sh @@ -137,9 +137,44 @@ HEAD_REPO="$(printf '%s' "$PR_METADATA" | python3 -c 'import json, sys; value=js BASE_REPO="$(printf '%s' "$PR_METADATA" | python3 -c 'import json, sys; value=json.load(sys.stdin).get("baseRepository") or ""; print((value.get("nameWithOwner") or value.get("full_name") or "") if isinstance(value, dict) else str(value).strip())')" PR_TITLE="$(printf '%s' "$PR_METADATA" | python3 -c 'import json, sys; print((json.load(sys.stdin).get("title") or "").strip())')" PR_AUTHOR="$(printf '%s' "$PR_METADATA" | python3 -c 'import json, sys; value=json.load(sys.stdin).get("author") or ""; print((value.get("login") or "").strip() if isinstance(value, dict) else str(value).strip())')" -if [[ "$BASE_BRANCH" != "main" && "$BASE_BRANCH" != "next" ]]; then - echo "Error: Mosaic policy allows merges only for PRs targeting 'main' or 'next' (found '$BASE_BRANCH')." >&2 - exit 1 +# T51 WP5b: transition validation against the declaration (spec 4.1/4.2). +# Target branches are validated against the declaration, NEVER hardcoded; +# the legacy main/next check survives only for undeclared repos during the +# rollout window (4.3 irreversible class, loud warning). +# shellcheck source=packages/mosaic/framework/tools/git/repo-decl.sh +if [ -f "$SCRIPT_DIR/repo-decl.sh" ]; then + source "$SCRIPT_DIR/repo-decl.sh" + repo_decl_load +else + DECL_STATE=absent; DECL_SCHEMA="" + repo_decl_warn() { printf 'repo-decl: %s\n' "$*" >&2; } + repo_decl_report_invalid() { :; } + repo_decl_warn_absent_reversible() { :; } + repo_decl_warn_absent_irreversible() { :; } + repo_decl_remote_matches() { return 0; } + repo_decl_check_transition() { return 2; } +fi +if [[ "$DECL_STATE" == invalid ]]; then + repo_decl_report_invalid +fi +if [[ "$DECL_STATE" == valid && "$DECL_SCHEMA" == 2 ]]; then + if ! repo_decl_remote_matches; then + echo "Error: origin remote does not match the declared canonical_remote (spec 5.3, write path) — refusing to merge against the wrong forge. Fix the origin remote or the declaration." >&2 + exit 1 + fi + trc=0 + repo_decl_check_transition "$HEAD_BRANCH" "$BASE_BRANCH" || trc=$? + if [[ "$trc" == 1 ]]; then + echo "Error: PR '$HEAD_BRANCH' -> '$BASE_BRANCH' is not a declared transition (flow=$DECL_FLOW, trunk=$DECL_TRUNK, release=$DECL_RELEASE; spec 4.2)." >&2 + exit 1 + fi + echo "repo-decl: transition OK under flow=$DECL_FLOW (trunk=$DECL_TRUNK release=$DECL_RELEASE)" >&2 +else + repo_decl_warn_absent_irreversible "pr-merge" + if [[ "$BASE_BRANCH" != "main" && "$BASE_BRANCH" != "next" ]]; then + echo "Error: Mosaic policy allows merges only for PRs targeting 'main' or 'next' (found '$BASE_BRANCH')." >&2 + exit 1 + fi fi if [[ -z "$HEAD_BRANCH" || -z "$HEAD_REPO" || ! "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then echo "Error: Could not resolve the PR head branch, repository, and full commit SHA for queue inspection." >&2 diff --git a/packages/mosaic/framework/tools/git/repo-decl.sh b/packages/mosaic/framework/tools/git/repo-decl.sh new file mode 100755 index 00000000..26a5d66c --- /dev/null +++ b/packages/mosaic/framework/tools/git/repo-decl.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# repo-decl.sh — shared .mosaic/repo.json consumption for the git wrappers (T51 WP5b). +# +# Spec of record: docs/plans/2026-08-23_repo-structure-declaration.md (brain +# repo) sections 4 (consumption contract), 5.3 (normalization), 5.4 +# (enforcement points), 1.2a (root anchoring). ALL consumers invoke the SAME +# WP1 validator (spec 5.1 — no in-process-only parsing of the declaration). +# +# Source this file, then call repo_decl_load once. It sets: +# DECL_STATE absent | invalid | valid +# DECL_FILE the declaration path that was inspected +# DECL_ERROR the validator's error line when DECL_STATE=invalid +# DECL_TRUNK / DECL_RELEASE / DECL_FLOW / DECL_REMOTE / DECL_POLICY / +# DECL_WT_ROOT / DECL_CLONE (populated only when DECL_STATE=valid) +# DECL_ORIGIN_N the normalized origin URL (when resolvable) +# +# Enforcement point 5.4(1): an invalid or unknown-version file counts as +# ABSENT for behavior, PLUS a loud error naming the file and the validator's +# key/reason — callers print DECL_ERROR (repo_decl_report_invalid) whenever +# they loaded something that failed validation; they do not silently ignore a +# broken file. +# +# Absence behavior (4.3) is the CALLER's policy (reversible vs irreversible; +# managed vs unmanaged — the adoption register is WP6, so during rollout every +# repo is unmanaged: warn + legacy). Helpers below provide the shared wordings. +# +# Root-dependent fields: consumers here read branch/flow/remote/policy only — +# NO path resolution happens in this library. The one helper that would +# resolve a host:/ path (repo_decl_path) fails closed while MOSAIC_HOST_ROOT +# is unset (1.2a: never guess a root), for any future caller that needs it. +# +# No output on success; diagnostics go to stderr. + +REPO_DECL_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DECL_VALIDATOR="$REPO_DECL_SCRIPT_DIR/../structure/validate-repo-json.sh" + +repo_decl_warn() { printf 'repo-decl: %s\n' "$*" >&2; } + +# Load and classify the declaration for the repo containing the current +# directory. Never fatal — classification is the product. +repo_decl_load() { + DECL_STATE=absent + DECL_FILE="" + DECL_ERROR="" + DECL_SCHEMA="" + DECL_TRUNK=""; DECL_RELEASE=""; DECL_FLOW=""; DECL_REMOTE="" + DECL_POLICY=""; DECL_WT_ROOT=""; DECL_CLONE="" + DECL_ORIGIN_N="" + + local root + root="$(git rev-parse --show-toplevel 2>/dev/null)" || { + repo_decl_warn "no git repository — declaration consumption skipped" + return 0 + } + DECL_FILE="$root/.mosaic/repo.json" + [ -f "$DECL_FILE" ] || return 0 + + if [ ! -x "$REPO_DECL_VALIDATOR" ] && [ ! -f "$REPO_DECL_VALIDATOR" ]; then + # 5.1 mandates the shared validator; a missing validator is an + # infrastructure failure, not an absent declaration. + repo_decl_warn "validator not found at $REPO_DECL_VALIDATOR — treating declaration as invalid" + DECL_STATE=invalid + DECL_ERROR="VALIDATION_ERROR validator: the shared validator is missing" + return 0 + fi + + local vout + if ! vout="$("$REPO_DECL_VALIDATOR" "$DECL_FILE" --mode display 2>&1)"; then + DECL_STATE=invalid + DECL_ERROR="$(printf '%s\n' "$vout" | grep -m1 'VALIDATION_ERROR' || printf '%s\n' "$vout" | head -1)" + return 0 + fi + + # Valid: extract the consumed fields via the same python stdlib the + # ecosystem already uses. Field-level grammar was the validator's job. + eval "$(python3 - "$DECL_FILE" <<'PY' +import json, sys +d = json.load(open(sys.argv[1])) +def q(k): + v = d.get(k, "") + return v if isinstance(v, str) else "" +sv = d.get("schema_version", 1) +print(f"DECL_SCHEMA={sv if isinstance(sv, int) and not isinstance(sv, bool) else 0!r}") +print(f"DECL_TRUNK={q('integration_trunk')!r}") +print(f"DECL_RELEASE={q('release_branch')!r}") +print(f"DECL_FLOW={q('flow')!r}") +print(f"DECL_REMOTE={q('canonical_remote')!r}") +print(f"DECL_POLICY={q('worktree_policy')!r}") +print(f"DECL_WT_ROOT={q('worktree_root')!r}") +print(f"DECL_CLONE={q('canonical_clone')!r}") +PY +)" || { + DECL_STATE=invalid + DECL_ERROR="VALIDATION_ERROR internal: field extraction failed" + return 0 + } + DECL_STATE=valid + + # 5.3: normalize origin once for remote comparisons (read callers warn, + # write callers refuse). An unresolvable origin is left empty — callers + # treat empty as "cannot compare" and act per their read/write policy. + local ourl + if ourl="$(git remote get-url origin 2>/dev/null)" && [ -n "$ourl" ]; then + DECL_ORIGIN_N="$("$REPO_DECL_VALIDATOR" --normalize-remote "$ourl" 2>/dev/null || true)" + fi + return 0 +} + +# The mandatory loud error for an invalid file (5.4 point 1). Callers invoke +# this whenever DECL_STATE=invalid, regardless of their proceed/refuse policy. +repo_decl_report_invalid() { + repo_decl_warn "declaration INVALID at $DECL_FILE — $DECL_ERROR" + repo_decl_warn "treating the declaration as ABSENT (spec 5.4); legacy behavior follows" +} + +# Shared absence wordings (4.3, rollout window: no adoption register yet, so +# every repo is unmanaged; warn + legacy per the Q-C ruling). +repo_decl_warn_absent_reversible() { # $1 = operation name + repo_decl_warn "no .mosaic/repo.json — $1 is unmanaged during rollout: legacy behavior, no declaration guarantees (spec 4.3)" +} +repo_decl_warn_absent_irreversible() { # $1 = operation name + repo_decl_warn "no .mosaic/repo.json — $1 proceeds on LEGACY assumptions during the rollout window; declaration-validated transitions unavailable (spec 4.3)" +} + +# Remote comparison (5.3). rc 0 match/unknown, rc 1 mismatch. +repo_decl_remote_matches() { + [ "$DECL_STATE" = valid ] || return 0 + [ -n "$DECL_ORIGIN_N" ] && [ -n "$DECL_REMOTE" ] || return 0 + local want + want="$("$REPO_DECL_VALIDATOR" --normalize-remote "$DECL_REMOTE" 2>/dev/null || true)" + [ -n "$want" ] || return 0 + [ "$DECL_ORIGIN_N" = "$want" ] +} + +# Resolve a host:/-anchored declaration path (1.2a). Fails CLOSED while +# MOSAIC_HOST_ROOT is unset or empty — never guesses a root. No WP5b consumer +# calls this today; it exists so the first one that needs a path cannot +# silently guess. +repo_decl_path() { # $1 = host:/... value; prints the resolved absolute path + local v="${1:-}" root="${MOSAIC_HOST_ROOT:-}" + case "$v" in + host:/*) ;; + *) return 1 ;; + esac + if [ -z "$root" ]; then + repo_decl_warn "MOSAIC_HOST_ROOT is unset — refusing to resolve '$v' (spec 1.2a fail-closed; never guess a root)" + return 1 + fi + printf '%s/%s\n' "${root%/}" "${v#host:/}" +} + +# Transition validation (4.2: a CLI flag is input, not authority). +# rc 0 = allowed; rc 1 = forbidden (message on stderr); rc 2 = no valid +# declaration (caller applies its absence policy). +# flow=direct: base must be the trunk (trunk == release); head must +# differ from it. +# flow=trunk-release: feature->trunk allowed; trunk->release allowed (release +# promotion: head IS the trunk); anything else refused — +# feature->release explicitly REJECTED. +repo_decl_check_transition() { # $1 head, $2 base + [ "$DECL_STATE" = valid ] || return 2 + local head="$1" base="$2" + if [ -z "$head" ] || [ -z "$base" ]; then + repo_decl_warn "transition check needs a head and a base (got head='$head' base='$base')" + return 1 + fi + if [ "$head" = "$base" ]; then + repo_decl_warn "forbidden transition: head '$head' equals base '$base'" + return 1 + fi + case "$DECL_FLOW" in + direct) + if [ "$base" = "$DECL_TRUNK" ]; then + return 0 + fi + repo_decl_warn "forbidden transition (flow=direct): base must be the trunk '$DECL_TRUNK', got '$base'" + return 1 + ;; + trunk-release) + if [ "$base" = "$DECL_TRUNK" ] && [ "$head" != "$DECL_TRUNK" ] && [ "$head" != "$DECL_RELEASE" ]; then + return 0 # feature -> trunk + fi + if [ "$head" = "$DECL_TRUNK" ] && [ "$base" = "$DECL_RELEASE" ]; then + return 0 # release promotion: trunk -> release + fi + if [ "$base" = "$DECL_RELEASE" ] && [ "$head" != "$DECL_TRUNK" ]; then + repo_decl_warn "forbidden transition (flow=trunk-release): feature->release is REJECTED (head '$head' -> release '$DECL_RELEASE'); promote via $DECL_TRUNK" + return 1 + fi + repo_decl_warn "forbidden transition (flow=trunk-release): '$head' -> '$base' is not a declared transition (feature->$DECL_TRUNK or $DECL_TRUNK->$DECL_RELEASE)" + return 1 + ;; + *) + repo_decl_warn "unknown declared flow '$DECL_FLOW'" + return 1 + ;; + esac +} diff --git a/packages/mosaic/framework/tools/git/test-repo-decl-consumption.sh b/packages/mosaic/framework/tools/git/test-repo-decl-consumption.sh new file mode 100755 index 00000000..31646b45 --- /dev/null +++ b/packages/mosaic/framework/tools/git/test-repo-decl-consumption.sh @@ -0,0 +1,333 @@ +#!/usr/bin/env bash +# test-repo-decl-consumption.sh — hermetic declaration-consumption suite for the +# git wrappers (T51 WP5b). +# +# Spec of record (brain repo): docs/plans/2026-08-23_repo-structure-declaration.md +# sections 4 (consumption), 5.3 (normalization), 5.4 (hostile-input classes), +# 1.2a (root anchoring). Covers every §5.4 class applicable to consumed fields +# plus the per-tool behaviors (base precedence, transition validation, remote +# fail-closed, absence policy, route context, staged worktree rule). +# +# Red-first usage: WP5B_TOOLS= bash $0 +# exits nonzero — the declaration-driven arms fail against tools that predate +# the change (evidence captured in the WP5b report). +# +# Hermetic: scratch repos under $TMPDIR, PATH-stubbed curl, sandboxed HOME; no +# network, no live forge, no writes outside the sandbox. + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TOOLS_SRC="${WP5B_TOOLS:-$SCRIPT_DIR}" +TROOT="${TMPDIR:-/tmp}" + +PASS=0 FAIL=0 FAILED_CASES="" +FIXTURES=() +cleanup_all() { local f; for f in "${FIXTURES[@]:-}"; do rm -rf -- "$f"; done; } +trap cleanup_all EXIT INT TERM + +ok() { PASS=$((PASS + 1)); } +bad() { FAIL=$((FAIL + 1)); FAILED_CASES="$FAILED_CASES $1"; printf 'FAIL: %s\n' "$1" >&2; } +assert_rc() { local d="$1" e="$2" a="$3"; [ "$e" = "$a" ] && ok || bad "$d (expected rc=$e got rc=$a)"; } +assert_eq() { local d="$1" e="$2" a="$3"; [ "$e" = "$a" ] && ok || bad "$d (expected [$e] got [$a])"; } +assert_contains() { local d="$1" h="$2" n="$3"; case "$h" in *"$n"*) ok ;; *) bad "$d (missing [$n])" ;; esac; } +assert_not_contains() { local d="$1" h="$2" n="$3"; case "$h" in *"$n"*) bad "$d (unexpected [$n])" ;; *) ok ;; esac; } +assert_count() { local d="$1" e="$2" a="$3"; [ "$e" = "$a" ] && ok || bad "$d (expected $e got $a)"; } + +new_sb() { SB="$(mktemp -d "$TROOT/wp5b-test.XXXXXX")"; FIXTURES+=("$SB"); } + +# A fixture repo with the (possibly pre-change) tools + validator installed. +# $1 dir, stdin = declaration JSON ("" = none), $2 = origin url ("" = none) +mkrepo() { + local d="$1" decl_origin="${2:-}" + mkdir -p "$d/tools/git" "$d/tools/structure" "$d/home" + cp "$TOOLS_SRC/repo-decl.sh" "$d/tools/git/" 2>/dev/null || true + cp "$TOOLS_SRC/pr-create.sh" "$TOOLS_SRC/pr-merge.sh" "$TOOLS_SRC/mosaic-worktree.sh" "$TOOLS_SRC/ci-queue-wait.sh" "$TOOLS_SRC/detect-platform.sh" "$d/tools/git/" 2>/dev/null || true + cp "$SCRIPT_DIR/../structure/validate-repo-json.sh" "$d/tools/structure/" + git -C "$d" init -q -b feature/x + git -C "$d" config user.name fixture; git -C "$d" config user.email fixture@test + mkdir -p "$d/.mosaic" "$d/sites" + printf 'base\n' > "$d/f"; git -C "$d" add -A; git -C "$d" commit -q -m base + [ -n "$decl_origin" ] && git -C "$d" remote add origin "$decl_origin" + cat > "$d/.mosaic/repo.json" + # whitespace-only input (the absent-decl arms use <<< "") must mean ABSENT, + # not an invalid-file fixture: strip it to no file. + grep -q '[^[:space:]]' "$d/.mosaic/repo.json" 2>/dev/null || rm -f "$d/.mosaic/repo.json" +} + +# The canonical v2 declaration used by default (custom branch names prove the +# tools never hardcode): trunk=dev-trunk release=prod-rel flow=trunk-release. +DECL_TR='{ + "schema_version": 2, + "integration_trunk": "dev-trunk", + "release_branch": "prod-rel", + "flow": "trunk-release", + "canonical_remote": "https://git.example.test/acme/widgets", + "canonical_clone": "host:/src/widgets", + "worktree_root": "host:/src/widgets-worktrees", + "worktree_policy": "orchestrator-precreated" +}' + +decl_direct() { printf '{\n "schema_version": 2,\n "integration_trunk": "mainline",\n "release_branch": "mainline",\n "flow": "direct",\n "canonical_remote": "https://git.example.test/acme/widgets",\n "canonical_clone": "host:/src/widgets"\n}\n'; } + +load_decl_in() { # $1 dir -> runs repo_decl_load in a subshell, prints STATE etc. + ( cd "$1" && source "$1/tools/git/repo-decl.sh" && repo_decl_load \ + && printf 'STATE=%s SCHEMA=%s TRUNK=%s RELEASE=%s FLOW=%s ERROR=%s\n' \ + "$DECL_STATE" "${DECL_SCHEMA:-}" "${DECL_TRUNK:-}" "${DECL_RELEASE:-}" "${DECL_FLOW:-}" "${DECL_ERROR:-}" ) +} + +echo "== A. lib classification + hostile inputs (spec 5.4 classes) ==" +new_sb; mkrepo "$SB/r1" <<< "$DECL_TR" "https://git.example.test/acme/widgets.git" +A="$(load_decl_in "$SB/r1")" +assert_contains "A1 valid v2 state" "$A" "STATE=valid" +assert_contains "A1 trunk" "$A" "TRUNK=dev-trunk" +assert_contains "A1 release" "$A" "RELEASE=prod-rel" +assert_contains "A1 flow" "$A" "FLOW=trunk-release" +assert_contains "A1 schema" "$A" "SCHEMA=2" + +new_sb; mkrepo "$SB/r2" <<< "" "https://git.example.test/acme/widgets" +A="$(load_decl_in "$SB/r2")" +assert_contains "A2 missing file = absent" "$A" "STATE=absent" + +new_sb; mkrepo "$SB/r3" <<< '{ not json' +A="$(load_decl_in "$SB/r3")" +assert_contains "A3 malformed JSON = invalid" "$A" "STATE=invalid" +assert_contains "A3 error names the failure" "$A" "VALIDATION_ERROR" + +new_sb; mkrepo "$SB/r4" <<< '{"schema_version": 99, "integration_trunk": "x", "release_branch": "y", "flow": "direct", "canonical_remote": "https://a/b"}' +A="$(load_decl_in "$SB/r4")" +assert_contains "A4 unknown schema_version = invalid" "$A" "STATE=invalid" + +new_sb; mkrepo "$SB/r5" <<< '{"integration_trunk": "x", "release_branch": "y"}' +A="$(load_decl_in "$SB/r5")" +assert_contains "A5 v1 validates" "$A" "STATE=valid" +assert_contains "A5 v1 schema recorded" "$A" "SCHEMA=1" + +new_sb; mkrepo "$SB/r6" <<< '{"schema_version": 2, "integration_trunk": "x", "release_branch": "y", "flow": "direct", "canonical_remote": "https://a/b", "surprise": 1}' +A="$(load_decl_in "$SB/r6")" +assert_contains "A6 unknown top-level key = invalid" "$A" "STATE=invalid" + +new_sb; mkrepo "$SB/r7" <<< '{"schema_version": 2, "integration_trunk": "bad..name", "release_branch": "y", "flow": "direct", "canonical_remote": "https://a/b"}' +A="$(load_decl_in "$SB/r7")" +assert_contains "A7 bad ref name = invalid" "$A" "STATE=invalid" + +new_sb; mkrepo "$SB/r8" <<< '{"schema_version": 2, "integration_trunk": "a", "release_branch": "b", "flow": "direct", "canonical_remote": "https://a/b"}' +A="$(load_decl_in "$SB/r8")" +assert_contains "A8 cross-field violation = invalid" "$A" "STATE=invalid" + +new_sb; mkrepo "$SB/r9" <<< '{"schema_version": 2, "integration_trunk": "a", "release_branch": "b", "flow": "trunk-release", "canonical_remote": "https://user:pw@a/b"}' +A="$(load_decl_in "$SB/r9")" +assert_contains "A9 userinfo URL = invalid" "$A" "STATE=invalid" + +echo "== A2. transitions + remote + path anchoring ==" +new_sb; mkrepo "$SB/t1" <<< "$DECL_TR" +T=(); rc=0 +T_out="$( cd "$SB/t1" && source tools/git/repo-decl.sh && repo_decl_load + repo_decl_check_transition feat dev-trunk && echo "feat->trunk:ALLOWED" + repo_decl_check_transition dev-trunk prod-rel && echo "trunk->rel:ALLOWED" + repo_decl_check_transition feat prod-rel || echo "feat->rel:REFUSED" + repo_decl_check_transition other other || echo "same:REFUSED" + repo_decl_check_transition feat elsewhere || echo "arbitrary:REFUSED" )" +assert_contains "T1 feature->trunk allowed" "$T_out" "feat->trunk:ALLOWED" +assert_contains "T1 trunk->release allowed" "$T_out" "trunk->rel:ALLOWED" +assert_contains "T1 feature->release refused" "$T_out" "feat->rel:REFUSED" +assert_contains "T1 head==base refused" "$T_out" "same:REFUSED" +assert_contains "T1 arbitrary target refused" "$T_out" "arbitrary:REFUSED" + +new_sb; mkrepo "$SB/t2" <<< "$(decl_direct)" +T_out="$( cd "$SB/t2" && source tools/git/repo-decl.sh && repo_decl_load + repo_decl_check_transition feat mainline && echo "direct-ok:ALLOWED" + repo_decl_check_transition feat other || echo "direct-other:REFUSED" )" +assert_contains "T2 direct feature->trunk allowed" "$T_out" "direct-ok:ALLOWED" +assert_contains "T2 direct other base refused" "$T_out" "direct-other:REFUSED" + +new_sb; mkrepo "$SB/t3" <<< "$DECL_TR" "https://Git.Example.Test/acme/widgets.git/" +M="$( cd "$SB/t3" && source tools/git/repo-decl.sh && repo_decl_load && repo_decl_remote_matches && echo MATCH )" +assert_contains "T3 normalization: .git/case differences still MATCH" "$M" "MATCH" +new_sb; mkrepo "$SB/t4" <<< "$DECL_TR" "https://git.example.test/acme/OTHER" +M="$( cd "$SB/t4" && source tools/git/repo-decl.sh && repo_decl_load && { repo_decl_remote_matches && echo MATCH; } || echo MISMATCH )" +assert_contains "T4 remote mismatch detected" "$M" "MISMATCH" + +new_sb; mkrepo "$SB/t5" <<< "$DECL_TR" +P="$( cd "$SB/t5" && source tools/git/repo-decl.sh && { repo_decl_path "host:/src/x" 2>/dev/null && echo RESOLVED; } || echo FAILCLOSED )" +assert_contains "T5 host:/ resolution fails closed (root unset)" "$P" "FAILCLOSED" + +echo "== B. pr-create consumption ==" +mkpr() { # $1 dir: install the curl stub + run env; sets PR_RC/PR_OUT/PR_ERR/PR_PAYLOAD + mkdir -p "$1/stub" + cat > "$1/stub/curl" <<'STUB' +#!/usr/bin/env bash +url="${*: -1}" +printf 'curl %s\n' "$*" >> "${STUB_DIR:?}/calls.log" +case "$url" in + */api/v1/repos/acme/widgets) printf '%s\n' '{"default_branch":"forge-default"}'; exit 0 ;; + */pulls) + while [[ $# -gt 0 ]]; do + case "$1" in -d) printf '%s' "$2" > "${STUB_DIR:?}/payload.json"; shift 2 ;; *) shift ;; esac + done + printf '%s\n' '{"number":42}'; exit 0 ;; + *) printf '%s\n' '{}'; exit 0 ;; +esac +STUB + chmod +x "$1/stub/curl" +} +run_pr() { # $1 dir, rest args -> pr-create + local prdir="$1"; shift + PR_RC=0 + PR_OUT="$(cd "$prdir" && env -i PATH="$prdir/stub:/usr/bin:/bin" HOME="$prdir/home" \ + GITEA_TOKEN=stub-token STUB_DIR="$prdir" \ + bash "$prdir/tools/git/pr-create.sh" "$@" < /dev/null 2>"$prdir/err.txt")" || PR_RC=$? + PR_ERR="$(cat "$prdir/err.txt")" + PR_PAYLOAD="$(cat "$prdir/payload.json" 2>/dev/null || true)" + PR_GETS="$(grep -c 'repos/acme/widgets$' "$prdir/calls.log" 2>/dev/null || true)"; PR_GETS="${PR_GETS:-0}" + PR_POSTS="$(grep -c '/pulls$' "$prdir/calls.log" 2>/dev/null || true)"; PR_POSTS="${PR_POSTS:-0}" + : > "$prdir/calls.log" 2>/dev/null || true + rm -f "$prdir/payload.json" +} + +new_sb; mkrepo "$SB/b1" <<< "$DECL_TR" "https://git.example.test/acme/widgets"; mkpr "$SB/b1" +git -C "$SB/b1" checkout -q -b feature/x 2>/dev/null || true +run_pr "$SB/b1" -t "T" +assert_rc "B1 declared trunk base rc 0" 0 "$PR_RC" +base="$(printf '%s' "$PR_PAYLOAD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("base",""))' 2>/dev/null || true)" +assert_eq "B1 payload base = declared trunk (no -B)" "dev-trunk" "$base" +assert_count "B1 zero repo GETs (declared trunk consulted, not the forge default)" 0 "$PR_GETS" + +run_pr "$SB/b1" -t "T" -B prod-rel +assert_rc "B2 -B feature->release REFUSED (4.2)" 1 "$PR_RC" +assert_contains "B2 names the transition rule" "$PR_ERR" "not an allowed transition" +assert_count "B2 no POST issued" 0 "$PR_POSTS" + +run_pr "$SB/b1" -t "T" -B dev-trunk +assert_rc "B3 -B feature->trunk allowed" 0 "$PR_RC" +base="$(printf '%s' "$PR_PAYLOAD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("base",""))' 2>/dev/null || true)" +assert_eq "B3 payload base = explicit allowed -B" "dev-trunk" "$base" + +run_pr "$SB/b1" -t "T" -B dev-trunk --head dev-trunk +assert_rc "B4 -B trunk->trunk (head==base) refused" 1 "$PR_RC" + +new_sb; mkrepo "$SB/b5" <<< "$DECL_TR" "https://git.example.test/acme/wrong"; mkpr "$SB/b5" +run_pr "$SB/b5" -t "T" +assert_rc "B5 remote mismatch on write path refuses" 1 "$PR_RC" +assert_contains "B5 names the 5.3 rule" "$PR_ERR" "canonical_remote" +assert_count "B5 no POST" 0 "$PR_POSTS" + +new_sb; mkrepo "$SB/b6" <<< "" "https://git.example.test/acme/widgets"; mkpr "$SB/b6" +run_pr "$SB/b6" -t "T" +assert_rc "B6 absent decl: legacy forge default rc 0" 0 "$PR_RC" +base="$(printf '%s' "$PR_PAYLOAD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("base",""))' 2>/dev/null || true)" +assert_eq "B6 payload base = forge default" "forge-default" "$base" +assert_count "B6 repo GET performed (WP5a floor preserved)" 1 "$PR_GETS" +assert_contains "B6 absence warning present" "$PR_ERR" "unmanaged during rollout" + +new_sb; mkrepo "$SB/b7" <<< '{ not json' "https://git.example.test/acme/widgets"; mkpr "$SB/b7" +run_pr "$SB/b7" -t "T" +assert_rc "B7 invalid decl: loud report + legacy proceed" 0 "$PR_RC" +assert_contains "B7 validation error reported" "$PR_ERR" "declaration INVALID" +base="$(printf '%s' "$PR_PAYLOAD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("base",""))' 2>/dev/null || true)" +assert_eq "B7 legacy forge default used" "forge-default" "$base" + +new_sb; mkrepo "$SB/b8" <<< '{"integration_trunk": "x", "release_branch": "y"}' "https://git.example.test/acme/widgets"; mkpr "$SB/b8" +run_pr "$SB/b8" -t "T" +assert_rc "B8 v1 decl: legacy proceed rc 0" 0 "$PR_RC" +assert_contains "B8 v1 note present" "$PR_ERR" "no consumable flow/trunk fields" +base="$(printf '%s' "$PR_PAYLOAD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("base",""))' 2>/dev/null || true)" +assert_eq "B8 legacy forge default used" "forge-default" "$base" + +echo "== C. pr-merge transition validation (no hardcoded targets) ==" +mkmerge() { # $1 dir, $2 base, $3 head -> stubs pr-metadata; runs pr-merge; sets M_RC/M_OUT/M_ERR + cp "$SCRIPT_DIR/pr-merge.sh" "$1/tools/git/pr-merge.sh" 2>/dev/null || true + cat > "$1/tools/git/pr-metadata.sh" < "$1/tools/git/ci-queue-wait.sh" <<'EOF' +#!/usr/bin/env bash +exit 0 +EOF + chmod +x "$1/tools/git/ci-queue-wait.sh" + mkdir -p "$1/stub" + printf '#!/usr/bin/env bash\nexit 0\n' > "$1/stub/curl"; chmod +x "$1/stub/curl" + M_RC=0 + M_OUT="$(cd "$1" && env -i PATH="$1/stub:/usr/bin:/bin" HOME="$1/home" GITEA_TOKEN=stub-token \ + bash "$1/tools/git/pr-merge.sh" -n 7 --dry-run < /dev/null 2>"$1/merr.txt")" || M_RC=$? + M_ERR="$(cat "$1/merr.txt")" +} + +new_sb; mkrepo "$SB/c1" <<< "$DECL_TR" "https://git.example.test/acme/widgets" +mkmerge "$SB/c1" dev-trunk feature/x +assert_rc "C1 feature->trunk under decl (custom trunk name, no hardcode)" 0 "$M_RC" +assert_contains "C1 transition context printed" "$M_ERR" "transition OK under flow=trunk-release" + +mkmerge "$SB/c1" prod-rel feature/x +assert_rc "C2 feature->release REJECTED under decl" 1 "$M_RC" +assert_contains "C2 names the declared transition rule" "$M_ERR" "not a declared transition" + +mkmerge "$SB/c1" prod-rel dev-trunk +assert_rc "C3 trunk->release promotion allowed" 0 "$M_RC" + +new_sb; mkrepo "$SB/c4" <<< "" "https://git.example.test/acme/widgets" +mkmerge "$SB/c4" main feature/x +assert_rc "C4 absent decl: legacy main/next check still enforced (main ok)" 0 "$M_RC" +assert_contains "C4 legacy warning present" "$M_ERR" "LEGACY assumptions" +mkmerge "$SB/c4" trunk-x feature/x +assert_rc "C4 absent decl: unknown target rejected by legacy check" 1 "$M_RC" + +new_sb; mkrepo "$SB/c5" <<< "$DECL_TR" "https://git.example.test/acme/wrong" +mkmerge "$SB/c5" dev-trunk feature/x +assert_rc "C5 remote mismatch refuses the merge" 1 "$M_RC" +assert_contains "C5 names 5.3" "$M_ERR" "canonical_remote" + +echo "== D. mosaic-worktree staged rule (4.4/4.5) ==" +mkwt() { # $1 dir: home outside the repo so derivation passes assert_not_home + mv "$1/home" "$SB/wthome" 2>/dev/null || true +} +new_sb; mkrepo "$SB/d1" <<< '{ not json'; mkwt "$SB/d1" +WT_RC=0 +WT_OUT="$(cd "$SB/d1" && env -i PATH="/usr/bin:/bin" HOME="$SB/wthome" \ + bash "$SB/d1/tools/git/mosaic-worktree.sh" new feat2 < /dev/null 2>"$SB/d1/wterr.txt")" || WT_RC=$? +assert_rc "D1 invalid decl fails branch-creation loud" 1 "$WT_RC" +assert_contains "D1 names the invalid declaration" "$(cat "$SB/d1/wterr.txt")" "INVALID" + +new_sb; mkrepo "$SB/d2" <<< ""; mkwt "$SB/d2" +WT_RC=0 +WT_OUT="$(cd "$SB/d2" && env -i PATH="/usr/bin:/bin" HOME="$SB/wthome" \ + bash "$SB/d2/tools/git/mosaic-worktree.sh" new feat3 < /dev/null 2>"$SB/d2/wterr.txt")" || WT_RC=$? +assert_rc "D2 absent decl: warn + proceed" 0 "$WT_RC" +assert_contains "D2 loud warning present" "$(cat "$SB/d2/wterr.txt")" "LEGACY assumptions" + +new_sb; mkrepo "$SB/d3" <<< "$DECL_TR"; mkwt "$SB/d3" +WT_RC=0 +WT_OUT="$(cd "$SB/d3" && env -i PATH="/usr/bin:/bin" HOME="$SB/wthome" \ + bash "$SB/d3/tools/git/mosaic-worktree.sh" new feat4 < /dev/null 2>"$SB/d3/wterr.txt")" || WT_RC=$? +assert_rc "D3 valid decl: policy advisory + proceed" 0 "$WT_RC" +assert_contains "D3 precreated policy note" "$(cat "$SB/d3/wterr.txt")" "orchestrator-precreated" +assert_contains "D3 placement stays derived (no decl path used)" "$(cat "$SB/d3/wterr.txt")" "TRANSITIONAL" + +echo "== E. ci-queue-wait route context (C4: context only, never a gate) ==" +new_sb; mkrepo "$SB/e1" <<< "$DECL_TR" "https://git.example.test/acme/widgets" +git -C "$SB/e1" checkout -q -b dev-trunk 2>/dev/null || { git -C "$SB/e1" branch -q dev-trunk; git -C "$SB/e1" checkout -q dev-trunk; } +E_RC=0 +E_OUT="$(cd "$SB/e1" && env -i PATH="/usr/bin:/bin" HOME="$SB/e1/home" \ + bash "$SB/e1/tools/git/ci-queue-wait.sh" < /dev/null 2>"$SB/e1/eerr.txt")" || E_RC=$? +E_ERR="$(cat "$SB/e1/eerr.txt")" +assert_contains "E1 route context names trunk head" "$E_ERR" "'dev-trunk' is a trunk (integration head) head" +assert_contains "E1 context names the flow" "$E_ERR" "flow=trunk-release" + +new_sb; mkrepo "$SB/e2" <<< "" "https://git.example.test/acme/widgets" +E_RC=0 +E_OUT="$(cd "$SB/e2" && env -i PATH="/usr/bin:/bin" HOME="$SB/e2/home" \ + bash "$SB/e2/tools/git/ci-queue-wait.sh" < /dev/null 2>"$SB/e2/eerr.txt")" || E_RC=$? +assert_not_contains "E2 absence is SILENT for the guard (N4)" "$(cat "$SB/e2/eerr.txt")" "repo-decl" + +cleanup_all +assert_count "final: zero scratch residue" 0 "$(ls -d "$TROOT"/wp5b-test.* 2>/dev/null | wc -l | tr -d ' ')" + +echo +echo "pass=$PASS fail=$FAIL" +if [ "$FAIL" -gt 0 ]; then + echo "FAILED CASES:$FAILED_CASES" + exit 1 +fi +echo "ALL GREEN" diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json index a6753269..b901e669 100644 --- a/packages/mosaic/package.json +++ b/packages/mosaic/package.json @@ -25,7 +25,7 @@ "lint": "eslint src", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests && pnpm run test:framework-shell", - "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-edit.sh && bash framework/tools/git/test-pr-create-fallback-default-base.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-no-status.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-ci-queue-wait-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh && bash framework/tools/_scripts/test-structure-anchor-check.sh && bash framework/tools/fleet/test-agent-session-broker-preflight.sh && bash framework/tools/fleet/test-agent-session-legacy-socket-guard.sh && bash framework/tools/git/test-grant-reviewer.sh" + "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-edit.sh && bash framework/tools/git/test-pr-create-fallback-default-base.sh && bash framework/tools/git/test-repo-decl-consumption.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-no-status.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-ci-queue-wait-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh && bash framework/tools/_scripts/test-structure-anchor-check.sh && bash framework/tools/fleet/test-agent-session-broker-preflight.sh && bash framework/tools/fleet/test-agent-session-legacy-socket-guard.sh && bash framework/tools/git/test-grant-reviewer.sh" }, "dependencies": { "@mosaicstack/brain": "workspace:*",