feat(git-tools): consume .mosaic/repo.json declarations in compat mode (T51 WP5b, closes #1413)
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
Spec of record: docs/plans/2026-08-23_repo-structure-declaration.md (brain repo) sections 4 (consumption contract), 5.1/5.3/5.4, 1.2a. New shared lib repo-decl.sh: one consumption surface for the wrappers. Loads .mosaic/repo.json, classifies absent/invalid/valid via the WP1 validator (5.1: ALL consumers invoke the same script; 5.4 point 1: invalid = ABSENT + loud error naming file/key/reason), extracts the consumed fields, normalizes origin for 5.3 comparisons, validates transitions per 4.2 (a CLI flag is input, not authority), and resolves host:/ paths FAIL-CLOSED while MOSAIC_HOST_ROOT is unset (1.2a — no WP5b consumer resolves a path today; the helper exists so the first that needs one cannot guess). v1 declarations validate but carry no consumable fields: legacy behavior with a note. pr-create.sh: base precedence -B (validated as an allowed transition) -> declared integration_trunk -> legacy WP5a forge-default floor (unmanaged/absent/v1 per 4.3 reversible class, warn + legacy). Remote mismatch vs canonical_remote refuses (write path, 5.3). pr-merge.sh: transition validation per declared flow; the hardcoded main/next target check survives only for undeclared repos during the rollout window (4.3 irreversible class, loud warning). Remote mismatch refuses. ci-queue-wait.sh: ROUTE CONTEXT only (4.1, C4/jarvis F8/DR2 R9) — branch-selection semantics untouched, absence silent, invalid reported per 5.4. mosaic-worktree.sh: staged rule 4.4 — invalid declaration fails branch-creation loud, absent warns and proceeds, valid contributes policy ADVICE only (4.5: placement stays derived; the advisory worktree_root comparison runs only when MOSAIC_HOST_ROOT is set, per 1.2a warn-and-omit). Consuming via a self-located source line and set -u-safe env access. mutate-push-guard.sh: NO change — spec 4.1 names it for push-to-trunk protection, but the tool as shipped is a mutation-coverage meta-tool for push-guard.sh with no trunk-protection logic to consult; the disposition is documented in #1413 rather than force-feeding a fake consumption. All wrappers degrade SILENTLY to legacy behavior when repo-decl.sh is absent from a copied tool subset (a legal deployment shape; a note there broke single-line diagnostic contracts in test-pr-merge-message-field). test-repo-decl-consumption.sh: 67 assertions, green x2, hermetic; runs RED against pre-change tools via WP5B_TOOLS (49 red there — red-first evidence). Covers every 5.4 hostile-input class applicable to consumed fields (missing, malformed, unknown schema_version, v1, unknown key, bad refs, cross-field, userinfo URL, remote mismatch) plus transition validation, base precedence, absence policies, staged worktree rule, route context, and 1.2a fail-closed. Enumerated on the S1 surface (enumeration guard green: population 71, enumerated 57). Neighbor suites green: WP5a fallback suite, all six pr-merge suites, worktree large-repo, help/login/interactive suites. S1 chain failures (fleet-units systemd bus, invariant_r host Pi version, pr-edit credential-helper env) reproduce identically at origin/next — environmental, untouched by this diff. No TS/vitest lane touched (shell tools only).
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user