diff --git a/.github/hooks/validate-k9.sh b/.github/hooks/validate-k9.sh index c83e290..24d4f56 100755 --- a/.github/hooks/validate-k9.sh +++ b/.github/hooks/validate-k9.sh @@ -4,16 +4,44 @@ # # validate-k9.sh — K9 configuration file validation script # -# Scans for .k9 and .k9.ncl files and validates: -# 1. K9! magic number on line 1 -# 2. Pedigree block presence with required fields (name, version) -# 3. Security level is one of: kennel, yard, hunt (case-insensitive) +# K9 files come in two dialects, validated differently (standards#434): +# +# Plain dialect (.k9) — text/YAML-ish documents. The `K9!` magic line is +# the format marker and MUST be the first non-empty line. Fields use +# `key: value` form. +# +# Nickel dialect (.k9.ncl) — Nickel source. A bare `K9!` line is a Nickel +# syntax error, so the format marker is carried differently: a +# `magic_number = "K9!"` field, a literal `K9!` preamble line (template +# files that are preprocessed before evaluation), or by construction — +# the file imports/merges a K9 pedigree schema (`K9Pedigree`, +# `pedigree_schema`, or an `import ".…k9.ncl"` of a base template that +# itself carries the magic). Library/contractile modules are that last +# class and are first-class citizens, not violations. +# +# Checks: +# 1. Format marker (dialect-appropriate, see above) +# 2. Pedigree presence with required fields (name; version as warning) +# 3. Security level (`leash`, `security_level`, or schema-v1 `trust_level`) +# is one of: kennel, yard, hunt (case-insensitive) # 4. Hunt-level files must have a signature or signature_required field # 5. SPDX-License-Identifier header presence # +# This is a LEXICAL linter (grep-grade), not a Nickel evaluator. Field +# checks are file-scope presence checks on purpose: Nickel lets authors +# factor the pedigree through `let` bindings and `&` merges, which no +# line-oriented block tracker can follow. (A previous version tracked +# brace depth to scope checks to the pedigree block; it missed every +# `let component_pedigree = {…}` factoring and miscounted its own opening +# brace. Do not reintroduce block scoping here — deep validation belongs +# to the Nickel contracts themselves.) +# # Environment variables: # INPUT_PATH — Directory to scan (default: .) # INPUT_STRICT — Promote warnings to errors (default: false) +# INPUT_PATHS_IGNORE — Newline-separated path fragments to skip. When the +# variable is unset, estate-safe defaults are used. Set +# it to an empty string to scan every matching file. # # Exit codes: # 0 — All files valid (or only warnings in non-strict mode) @@ -27,32 +55,129 @@ set -euo pipefail SCAN_PATH="${INPUT_PATH:-.}" STRICT="${INPUT_STRICT:-false}" -PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" -GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" - -# Parse paths-ignore: newline-separated fragments, blank lines and # comments -# stripped. Each fragment is a substring match against the file path. Pattern -# adopted from hyperpolymath/hypatia#243 — content-pattern validators must -# distinguish a target from a vendored / fixture file that legitimately -# contains the very pattern being checked. + +# Some estate files share the .k9/.k9.ncl suffix without being pedigree +# contracts. Others are deliberately invalid fixtures or vendored copies that +# belong to a different repository's validation boundary. These defaults were +# introduced in 47cca67, then accidentally lost when fa30044 absorbed the +# standards implementation. Keep the policy here as well as in action.yml so +# direct/pre-push invocations behave the same as the GitHub Action. +DEFAULT_PATHS_IGNORE=$'vendor/\nvendored/\nverified-container-spec/\n.audittraining/\nintegration/fixtures/\ntest/fixtures/\ntests/fixtures/\nabsolute-zero/\ncoordination.k9\nsession/custom-checks.k9\nself-validating/methodology-guard.k9.ncl' +if [[ ${INPUT_PATHS_IGNORE+x} == x ]]; then + PATHS_IGNORE_RAW="$INPUT_PATHS_IGNORE" +else + PATHS_IGNORE_RAW="$DEFAULT_PATHS_IGNORE" +fi + PATHS_IGNORE=() -while IFS= read -r _frag; do - # Strip leading and trailing whitespace (canonical bash idiom). - _frag="${_frag#"${_frag%%[![:space:]]*}"}" - _frag="${_frag%"${_frag##*[![:space:]]}"}" - [[ -z "$_frag" || "$_frag" == \#* ]] && continue - PATHS_IGNORE+=("$_frag") +while IFS= read -r fragment; do + fragment="${fragment#"${fragment%%[![:space:]]*}"}" + fragment="${fragment%"${fragment##*[![:space:]]}"}" + [[ -z "$fragment" || "$fragment" == \#* ]] && continue + PATHS_IGNORE+=("$fragment") done <<< "$PATHS_IGNORE_RAW" -# Returns 0 if path should be skipped (matches any ignore fragment) +# Check if a file path matches any configured ignore fragment. +# Returns 0 (true) if the path should be skipped, 1 (false) otherwise. path_ignored() { - local p="$1" frag - for frag in "${PATHS_IGNORE[@]}"; do - [[ "$p" == *"$frag"* ]] && return 0 + local path="$1" fragment + for fragment in "${PATHS_IGNORE[@]}"; do + [[ "$path" == *"$fragment"* ]] && return 0 done return 1 } +# Remove K9/YAML/Nickel line comments while preserving hashes inside +# double-quoted strings. SPDX detection deliberately continues to use the raw +# file because its marker is itself a comment. +strip_k9_comment() { + local input="$1" output="" char + local in_double=false escaped=false i + for ((i = 0; i < ${#input}; i++)); do + char="${input:i:1}" + if [[ "$escaped" == "true" ]]; then + output+="$char" + escaped=false + elif [[ "$in_double" == "true" && "$char" == "\\" ]]; then + output+="$char" + escaped=true + elif [[ "$char" == '"' ]]; then + output+="$char" + if [[ "$in_double" == "true" ]]; then + in_double=false + else + in_double=true + fi + elif [[ "$char" == '#' && "$in_double" == "false" ]]; then + break + else + output+="$char" + fi + done + printf '%s\n' "$output" +} + +comment_free_file() { + local file="$1" line + while IFS= read -r line; do + strip_k9_comment "$line" + done < "$file" +} + +# Replace double-quoted string contents with spaces before classifying a file. +# Schema words in prose or metadata values are not reachable K9 syntax, while +# structural expressions such as `pedigree.K9Pedigree` remain visible. +mask_k9_quoted_text() { + local input="$1" output="" char + local in_double=false escaped=false i + for ((i = 0; i < ${#input}; i++)); do + char="${input:i:1}" + if [[ "$in_double" == "true" ]]; then + if [[ "$escaped" == "true" ]]; then + output+=" " + escaped=false + elif [[ "$char" == "\\" ]]; then + output+=" " + escaped=true + elif [[ "$char" == '"' ]]; then + output+="$char" + in_double=false + else + output+=" " + fi + elif [[ "$char" == '"' ]]; then + output+="$char" + in_double=true + else + output+="$char" + fi + done + printf '%s\n' "$output" +} + +structural_syntax_file() { + local file="$1" line + while IFS= read -r line; do + mask_k9_quoted_text "$(strip_k9_comment "$line")" + done < "$file" +} + +# Check if a file is a K9 pedigree contract by searching for unambiguous +# pedigree signals (K9!, magic_number, pedigree metadata, or K9 schema reference). +# Returns 0 (true) if the file is a pedigree contract, 1 (false) otherwise. +is_pedigree_contract() { + local file="$1" syntax_content + syntax_content=$(structural_syntax_file "$file") + grep -Eq \ + '^[[:space:]]*K9![[:space:]]*$|^[[:space:]]*magic_number[[:space:]]*[=:]|^[[:space:]]*(let[[:space:]]+)?[A-Za-z_]*pedigree[[:space:]]*=|^[[:space:]]*(metadata|pedigree):[[:space:]]*$|K9Pedigree|pedigree_schema' \ + <<< "$syntax_content" +} + +# Outside GitHub Actions GITHUB_OUTPUT is unset; under `set -u` an unset +# expansion inside a redirection aborts the whole script (the `|| true` +# cannot catch an expansion error). Default to /dev/null for local runs. +GITHUB_OUTPUT="${GITHUB_OUTPUT:-/dev/null}" + # Counters FILES_SCANNED=0 ERRORS=0 @@ -91,19 +216,25 @@ report_issue() { # --------------------------------------------------------------------------- # Helper: normalise a security level string # --------------------------------------------------------------------------- -# Strips quotes, leading/trailing whitespace, Nickel enum tick prefix +# Strips quotes, leading/trailing whitespace, Nickel enum tick prefix. +# Handles both separators: `leash = 'Hunt` (Nickel) and `leash: hunt` (plain). normalise_level() { local raw="$1" # Remove surrounding quotes, tick prefix ('Kennel -> Kennel), whitespace - raw="${raw#*=}" # Remove everything before = + if [[ "$raw" == *"="* ]]; then + raw="${raw#*=}" # Remove everything before = + else + raw="${raw#*:}" # Plain dialect: remove everything before : + fi raw="${raw//\"/}" # Remove double quotes raw="${raw//\'/}" # Remove single quotes (Nickel tick) raw="${raw//,/}" # Remove trailing commas - raw="${raw## }" # Trim leading space - raw="${raw%% }" # Trim trailing space raw="${raw%%#*}" # Remove inline comments - raw="${raw## }" # Trim again - raw="${raw%% }" + # Trim ALL leading/trailing whitespace (a single-space `%% ` pattern + # strips only one char and let `'Kennel # comment` survive as + # "kennel " — an invalid-level false positive) + raw="${raw#"${raw%%[![:space:]]*}"}" + raw="${raw%"${raw##*[![:space:]]}"}" echo "${raw,,}" # Lowercase } @@ -112,9 +243,17 @@ normalise_level() { # --------------------------------------------------------------------------- validate_k9() { local file="$1" + local syntax_content + syntax_content=$(comment_free_file "$file") FILES_SCANNED=$((FILES_SCANNED + 1)) - # --- Check 1: K9! magic number on first non-empty line --- + # Dialect: .k9.ncl is Nickel source; bare .k9 is the plain dialect. + local dialect="plain" + if [[ "$file" == *.k9.ncl ]]; then + dialect="ncl" + fi + + # --- Check 1: format marker (dialect-appropriate) --- local first_content_line="" local first_content_line_num=0 local line_num=0 @@ -130,9 +269,28 @@ validate_k9() { break done < "$file" - if [[ "$first_content_line" != "K9!" ]]; then - report_issue "error" "$file" "$first_content_line_num" \ - "Missing K9! magic number. First non-empty line must be exactly 'K9!'" + if [[ "$dialect" == "plain" ]]; then + if [[ "$first_content_line" != "K9!" ]]; then + report_issue "error" "$file" "$first_content_line_num" \ + "Missing K9! magic number. First non-empty line must be exactly 'K9!'" + fi + else + # Nickel dialect: a bare K9! line is a Nickel syntax error, so the + # marker may instead be a magic_number field or arrive by construction + # through a pedigree-schema import/merge (library modules, #434). + local has_marker=false + if [[ "$first_content_line" == "K9!" ]]; then + has_marker=true + elif grep -Eq '^[[:space:]]*magic_number[[:space:]]*=[[:space:]]*"K9!"' <<< "$syntax_content"; then + has_marker=true + elif grep -Eq '(K9Pedigree|pedigree_schema)([[:space:]]*&|[[:space:]]*\{)|&[[:space:]]*(.*\.)?(K9Pedigree|pedigree_schema)|import[[:space:]]*"[^"]*(pedigree|\.k9)\.ncl"' <<< "$syntax_content"; then + has_marker=true + fi + + if [[ "$has_marker" == "false" ]]; then + report_issue "error" "$file" "$first_content_line_num" \ + "Missing K9 format marker. A .k9.ncl file needs a magic_number = \"K9!\" field, a K9! preamble line, or a K9 pedigree schema import/application/merge" + fi fi # --- Check 2: SPDX header --- @@ -154,99 +312,97 @@ validate_k9() { "Missing SPDX-License-Identifier in first 10 lines" fi - # --- Check 3: Pedigree block with required fields --- + # --- Check 3: Pedigree presence with required fields --- + # File-scope scans by design (see header): Nickel factoring means the + # pedigree may be `pedigree = {…}`, a let-bound `let component_pedigree + # = {…}`, a schema merge `X.pedigree_schema & {…}` / `X.K9Pedigree & + # {…}`, or — plain dialect — a `metadata:`/`pedigree:` YAML block. local has_pedigree=false local has_pedigree_name=false local has_pedigree_version=false local has_security_level=false + local has_primary_security_level=false local security_level_value="" local security_level_line=0 local has_signature_field=false - local in_pedigree=false - local pedigree_depth=0 + local in_policy=false + local policy_depth=0 line_num=0 while IFS= read -r line; do line_num=$((line_num + 1)) - # Detect pedigree block start. Note: do NOT `continue` here — the - # `pedigree = {` line itself contains the opening brace that - # establishes the block. Falling through to the brace counter - # below makes depth start at 1, so a subsequent `security = {…},` - # closing brace correctly takes depth to 1 (not 0), keeping us - # inside the pedigree block when later fields (name/version/leash) - # are checked. Previously the `continue` skipped this opening - # brace, depth started at 0, and the first nested block's close - # prematurely terminated the validator's view of the pedigree — - # making `pedigree.metadata.name` invisible. - if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then + if [[ "$line" =~ (^|[[:space:]\{,])policy[[:space:]]*=[[:space:]]*\{ ]]; then + in_policy=true + policy_depth=0 + fi + + # Pedigree construct, Nickel forms: direct, let-bound, schema + # application (`K9Pedigree { ... }`), or schema merge. + if [[ "$line" =~ ^[[:space:]]*(let[[:space:]]+)?[A-Za-z_]*pedigree[[:space:]]*= ]] \ + || [[ "$line" =~ (K9Pedigree|pedigree_schema)[[:space:]]*\{ ]] \ + || [[ "$line" =~ (K9Pedigree|pedigree_schema)[[:space:]]*\& ]] \ + || [[ "$line" =~ \&[[:space:]]*([A-Za-z_][A-Za-z0-9_]*\.)?(K9Pedigree|pedigree_schema) ]]; then has_pedigree=true - in_pedigree=true - pedigree_depth=0 - # fall through fi - if [[ "$in_pedigree" == "true" ]]; then - # Track brace depth to know when pedigree block ends - local opens closes - opens="${line//[^\{]/}" - closes="${line//[^\}]/}" - pedigree_depth=$(( pedigree_depth + ${#opens} - ${#closes} )) + # Pedigree construct, plain dialect: top-level metadata:/pedigree: block + if [[ "$dialect" == "plain" ]] \ + && [[ "$line" =~ ^(metadata|pedigree):[[:space:]]*$ ]]; then + has_pedigree=true + fi - if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" ]]; then - # Check this final line too before leaving - : - fi + # Required fields, either separator (= Nickel, : plain) + if [[ "$line" =~ (^|[[:space:]\{,])name[[:space:]]*[=:] ]]; then + has_pedigree_name=true + fi - # Check for name field within pedigree.metadata or pedigree directly. - # Two patterns cover both multi-line and single-line pedigrees: - # 1. ^[[:space:]]+name[[:space:]]*= — the normal multi-line case where - # `name = "..."` appears on its own indented line. - # 2. [[:space:]]name[[:space:]]*= — inline within a single-line - # pedigree assignment such as: - # pedigree = component_pedigree & { name = "foo" } - # (root cause: developer-ecosystem@baab1534 — single-line form - # was missed entirely because the pedigree block opened and - # closed in one line, never reaching the ^[[:space:]]+ check on - # a subsequent iteration.) - if [[ "$line" =~ ^[[:space:]]+name[[:space:]]*= ]] || \ - [[ "$line" =~ [[:space:]]name[[:space:]]*= ]]; then - has_pedigree_name=true - fi + if [[ "$line" =~ (^|[[:space:]\{,])(version|schema_version)[[:space:]]*[=:] ]]; then + has_pedigree_version=true + fi - # Check for version field - if [[ "$line" =~ ^[[:space:]]+(version|schema_version)[[:space:]]*= ]] || \ - [[ "$line" =~ [[:space:]](version|schema_version)[[:space:]]*= ]]; then - has_pedigree_version=true + # Security level (leash field) + if [[ "$line" =~ (^|[[:space:]\{,])(leash|security_level)[[:space:]]*[=:][[:space:]]*([^,\}\#]+) ]]; then + has_security_level=true + has_primary_security_level=true + security_level_value="$(normalise_level "${BASH_REMATCH[0]}")" + security_level_line=$line_num + elif [[ "$has_primary_security_level" == "false" ]]; then + local trust_is_policy=false + if [[ "$line" =~ policy[[:space:]]*\.[[:space:]]*trust_level[[:space:]]*[=:] ]] \ + || [[ "$in_policy" == "true" && "$line" =~ (^|[[:space:]\{,])trust_level[[:space:]]*[=:] ]] \ + || [[ "$line" =~ (^|[[:space:]\{,])policy[[:space:]]*=[[:space:]]*\{[^\}]*trust_level[[:space:]]*[=:] ]]; then + trust_is_policy=true fi - - # Check for security level (leash field) - if [[ "$line" =~ ^[[:space:]]+(leash|security_level)[[:space:]]*= ]]; then + if [[ "$trust_is_policy" == "true" ]] \ + && [[ "$line" =~ trust_level[[:space:]]*[=:][[:space:]]*([^,\}]+) ]]; then + # Only schema-v1 policy.trust_level is a leash. Legacy + # pedigree-level trust_level is descriptive metadata. has_security_level=true - security_level_value="$(normalise_level "$line")" + security_level_value="$(normalise_level "${BASH_REMATCH[0]}")" security_level_line=$line_num fi - - # Check for signature fields - if [[ "$line" =~ ^[[:space:]]+(signature|signature_required)[[:space:]]*= ]]; then - has_signature_field=true - fi - - # End of pedigree block - if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" && "$line" == *"}"* ]]; then - in_pedigree=false - fi fi - # Also check for signature fields outside pedigree (top-level) - if [[ "$line" =~ ^[[:space:]]*(signature)[[:space:]]*= ]]; then + # Signature fields + if [[ "$line" =~ (^|[[:space:]\{,])(signature|signature_required)[[:space:]]*[=:] ]]; then has_signature_field=true fi - done < "$file" + + if [[ "$in_policy" == "true" ]]; then + local without_open="${line//\{/}" + local without_close="${line//\}/}" + policy_depth=$((policy_depth + ${#line} - ${#without_open} - ${#line} + ${#without_close})) + if (( policy_depth <= 0 )); then + in_policy=false + policy_depth=0 + fi + fi + done <<< "$syntax_content" if [[ "$has_pedigree" == "false" ]]; then report_issue "error" "$file" 1 \ - "Missing pedigree block. K9 files must contain a 'pedigree = { ... }' section" + "Missing pedigree. K9 files need a pedigree section: 'pedigree = { ... }', a pedigree-schema merge, or (plain dialect) a 'metadata:' block" else if [[ "$has_pedigree_name" == "false" ]]; then report_issue "error" "$file" 1 \ @@ -260,7 +416,14 @@ validate_k9() { fi # --- Check 4: Security level validation --- - if [[ "$has_security_level" == "true" ]]; then + if [[ "$has_security_level" == "true" && "$security_level_value" =~ ^\{\{.*\}\}$ ]]; then + # Scaffold file: the level is a template placeholder to be filled at + # instantiation time. Note it, but a template cannot validate as + # concrete and flagging it every run just trains people to ignore + # the gate. + annotate "notice" "$file" "$security_level_line" \ + "Security level is a template placeholder (${security_level_value}); skipping level validation" + elif [[ "$has_security_level" == "true" ]]; then local level_valid=false for valid in $VALID_LEVELS; do if [[ "$security_level_value" == "$valid" ]]; then @@ -295,29 +458,40 @@ echo "::group::K9 Configuration Validation" echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..." echo "" -# Find all K9 files, excluding .git directory +# Find all K9 files, excluding .git and non-target paths. mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort) -# Apply paths-ignore filter k9_files=() -SKIPPED=0 -for _f in "${k9_candidates[@]}"; do - if path_ignored "$_f"; then - SKIPPED=$((SKIPPED + 1)) +FILES_SKIPPED=0 +FILES_SKIPPED_PATH=0 +FILES_SKIPPED_NON_CONTRACT=0 +for file in "${k9_candidates[@]}"; do + if path_ignored "$file"; then + FILES_SKIPPED=$((FILES_SKIPPED + 1)) + FILES_SKIPPED_PATH=$((FILES_SKIPPED_PATH + 1)) + echo "::notice file=${file}::Skipped non-target K9 path" + continue + fi + if ! is_pedigree_contract "$file"; then + FILES_SKIPPED=$((FILES_SKIPPED + 1)) + FILES_SKIPPED_NON_CONTRACT=$((FILES_SKIPPED_NON_CONTRACT + 1)) + echo "::notice file=${file}::Skipped K9-suffixed file with no pedigree-contract signal" continue fi - k9_files+=("$_f") + k9_files+=("$file") done -if [[ $SKIPPED -gt 0 ]]; then - echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" +if [[ $FILES_SKIPPED -gt 0 ]]; then + echo "Skipped ${FILES_SKIPPED} non-target K9 file(s) (${FILES_SKIPPED_PATH} by path, ${FILES_SKIPPED_NON_CONTRACT} without a pedigree signal)" + echo "" fi if [[ ${#k9_files[@]} -eq 0 ]]; then echo "::notice::No K9 files found in ${SCAN_PATH}" - echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "files_scanned=0" >> "$GITHUB_OUTPUT" 2>/dev/null || true + echo "files_skipped=${FILES_SKIPPED}" >> "$GITHUB_OUTPUT" 2>/dev/null || true + echo "errors=0" >> "$GITHUB_OUTPUT" 2>/dev/null || true + echo "warnings=0" >> "$GITHUB_OUTPUT" 2>/dev/null || true echo "::endgroup::" exit 0 fi @@ -333,6 +507,7 @@ done echo "" echo "────────────────────────────────────────" echo "Files scanned: ${FILES_SCANNED}" +echo "Files skipped: ${FILES_SKIPPED}" echo "Errors: ${ERRORS}" echo "Warnings: ${WARNINGS}" echo "Strict mode: ${STRICT}" @@ -341,9 +516,10 @@ echo "──────────────────────── # Write outputs for GitHub Actions { echo "files_scanned=${FILES_SCANNED}" + echo "files_skipped=${FILES_SKIPPED}" echo "errors=${ERRORS}" echo "warnings=${WARNINGS}" -} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true +} >> "$GITHUB_OUTPUT" 2>/dev/null || true echo "::endgroup::" diff --git a/docs/governance/MAINTENANCE-CHECKLIST.a2ml b/docs/governance/MAINTENANCE-CHECKLIST.a2ml deleted file mode 100644 index eaee720..0000000 --- a/docs/governance/MAINTENANCE-CHECKLIST.a2ml +++ /dev/null @@ -1,159 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Cross-repo maintenance baseline (machine-readable canonical) - -[metadata] -version = "1.1.0" -last-updated = "2026-02-24" -scope = "cross-repo" -source-human = "docs/maintenance/MAINTENANCE-CHECKLIST.adoc" -companion-human = "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc" -companion-machine = ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml" - -[policy] -single-source = true -notes = "Use this file as canonical machine policy and keep markdown synchronized." - -[maintenance-axes] -scoping-first = true -execution-order = ["scoping", "axis-1", "axis-2", "axis-3"] -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" - -[scoping] -inputs_required = [ - "README", - "roadmap", - "status-docs", - "maintenance-checklist", - "ci-and-security-docs", -] - -marker_scan_required = [ - "TODO", - "FIXME", - "XXX", - "HACK", - "STUB", - "PARTIAL", -] - -idris_unsound_scan_required = [ - "believe_me", - "assert_total", -] - -scope_assembly_buckets = ["must", "intend", "like"] - -[axis-2-maintenance-rules] -corrective-first = true -adaptive-second = true -adaptive_examples = [ - "scope-change reconciliation", - "stale-reference removal", - "obsolete-work culling", -] -perfective-third = true -perfective_source = "axis-1 honest state after corrective/adaptive updates" - -[axis-3-audit-rules] -systems-check = true -documentation-honesty-check = true -safety-security-accounted-check = true -effects-review-check = true -benchmark-evidence-required = true -maintainer-dialogue-review-required = true -compliance-seams-check = true -exception-register-required = true -exception-bounded-scope-required = true -policy-drift-contamination-check = true -example-drift-risk = "single TypeScript exception causing broad ReScript->TypeScript migration" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" - -[generic-cleanup-finish-off] -root-cleanup-required = true -stale-work-cull-required = true -docs-parity-required = true -machine-human-sync-required = true -compliance-finish-off-required = true -effects-finish-off-required = true -release-prep-summary-required = true -next-actions-required = ["corrective", "adaptive", "perfective"] - -[must] -root_control_files = [ - ".gitignore", - ".gitattributes", - ".editorconfig", - ".tool-versions", - "Containerfile", - "Justfile", -] - -root_hosting_files = [ - "CNAME", - ".nojekyll", -] - -ownership_files = [ - "MAINTAINER", - ".github/CODEOWNERS", -] - -machine_readable_required = [ - ".machine_readable/anchors/ANCHOR.a2ml", - ".machine_readable/contractiles/", - ".machine_readable/ai/", - ".machine_readable/bot_directives/", -] - -contractiles_required = [ - "Mustfile", - "Trustfile", - "Intentfile", -] - -security_required = [ - ".well-known/security.txt", - "ci-security-scan", -] - -quality_gate_required = [ - "format", - "lint", - "unit-tests", - "integration-tests", - "p2p-tests", - "e2e-tests", - "bench-smoke", - "docs-check", - "security-scan", -] - -abi_ffi_policy = [ - "ABI Idris2 in src/interface/abi/*.idr", - "FFI Zig in ffi/**/*.zig", -] - -[should] -docs_primary_format = "adoc" -docs_structure = [ - "docs/theory", - "docs/practice", - "docs/whitepapers/academic", - "docs/whitepapers/industry", - "docs/proofs", - "docs/reports", -] - -root_minimization = true -well_known_metadata = true -roadmap_honesty_with_dates = true -ci_doc_format_policy = true - -[could] -generate_human_from_machine = true -mode_aware_bots = ["corrective", "adaptive", "perfective", "audit"] -topology_dashboard = true -exception_registry = true diff --git a/satellites/a2mliser/docs/governance/MAINTENANCE-CHECKLIST.a2ml b/satellites/a2mliser/docs/governance/MAINTENANCE-CHECKLIST.a2ml deleted file mode 100644 index eaee720..0000000 --- a/satellites/a2mliser/docs/governance/MAINTENANCE-CHECKLIST.a2ml +++ /dev/null @@ -1,159 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Cross-repo maintenance baseline (machine-readable canonical) - -[metadata] -version = "1.1.0" -last-updated = "2026-02-24" -scope = "cross-repo" -source-human = "docs/maintenance/MAINTENANCE-CHECKLIST.adoc" -companion-human = "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc" -companion-machine = ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml" - -[policy] -single-source = true -notes = "Use this file as canonical machine policy and keep markdown synchronized." - -[maintenance-axes] -scoping-first = true -execution-order = ["scoping", "axis-1", "axis-2", "axis-3"] -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" - -[scoping] -inputs_required = [ - "README", - "roadmap", - "status-docs", - "maintenance-checklist", - "ci-and-security-docs", -] - -marker_scan_required = [ - "TODO", - "FIXME", - "XXX", - "HACK", - "STUB", - "PARTIAL", -] - -idris_unsound_scan_required = [ - "believe_me", - "assert_total", -] - -scope_assembly_buckets = ["must", "intend", "like"] - -[axis-2-maintenance-rules] -corrective-first = true -adaptive-second = true -adaptive_examples = [ - "scope-change reconciliation", - "stale-reference removal", - "obsolete-work culling", -] -perfective-third = true -perfective_source = "axis-1 honest state after corrective/adaptive updates" - -[axis-3-audit-rules] -systems-check = true -documentation-honesty-check = true -safety-security-accounted-check = true -effects-review-check = true -benchmark-evidence-required = true -maintainer-dialogue-review-required = true -compliance-seams-check = true -exception-register-required = true -exception-bounded-scope-required = true -policy-drift-contamination-check = true -example-drift-risk = "single TypeScript exception causing broad ReScript->TypeScript migration" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" - -[generic-cleanup-finish-off] -root-cleanup-required = true -stale-work-cull-required = true -docs-parity-required = true -machine-human-sync-required = true -compliance-finish-off-required = true -effects-finish-off-required = true -release-prep-summary-required = true -next-actions-required = ["corrective", "adaptive", "perfective"] - -[must] -root_control_files = [ - ".gitignore", - ".gitattributes", - ".editorconfig", - ".tool-versions", - "Containerfile", - "Justfile", -] - -root_hosting_files = [ - "CNAME", - ".nojekyll", -] - -ownership_files = [ - "MAINTAINER", - ".github/CODEOWNERS", -] - -machine_readable_required = [ - ".machine_readable/anchors/ANCHOR.a2ml", - ".machine_readable/contractiles/", - ".machine_readable/ai/", - ".machine_readable/bot_directives/", -] - -contractiles_required = [ - "Mustfile", - "Trustfile", - "Intentfile", -] - -security_required = [ - ".well-known/security.txt", - "ci-security-scan", -] - -quality_gate_required = [ - "format", - "lint", - "unit-tests", - "integration-tests", - "p2p-tests", - "e2e-tests", - "bench-smoke", - "docs-check", - "security-scan", -] - -abi_ffi_policy = [ - "ABI Idris2 in src/interface/abi/*.idr", - "FFI Zig in ffi/**/*.zig", -] - -[should] -docs_primary_format = "adoc" -docs_structure = [ - "docs/theory", - "docs/practice", - "docs/whitepapers/academic", - "docs/whitepapers/industry", - "docs/proofs", - "docs/reports", -] - -root_minimization = true -well_known_metadata = true -roadmap_honesty_with_dates = true -ci_doc_format_policy = true - -[could] -generate_human_from_machine = true -mode_aware_bots = ["corrective", "adaptive", "perfective", "audit"] -topology_dashboard = true -exception_registry = true