diff --git a/.githooks/validate-k9.sh b/.githooks/validate-k9.sh index 72b79f5..ea16916 100755 --- a/.githooks/validate-k9.sh +++ b/.githooks/validate-k9.sh @@ -164,12 +164,26 @@ validate_k9() { local has_signature_field=false local in_pedigree=false local pedigree_depth=0 + local pedigree_alias="" + + # Resolve a simple top-level pedigree alias (for example, + # `pedigree = component_pedigree`) before scanning the file. The aliased + # block can be declared before the exported record, so this must be a + # separate pass. + while IFS= read -r line; do + if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*=[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*,?[[:space:]]*(#.*)?$ ]]; then + pedigree_alias="${BASH_REMATCH[1]}" + has_pedigree=true + break + fi + done < "$file" line_num=0 while IFS= read -r line; do line_num=$((line_num + 1)) - # Detect pedigree block start. Note: do NOT `continue` here — the + # Detect a literal pedigree block or the declaration of a resolved + # pedigree alias. 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 = {…},` @@ -179,7 +193,8 @@ validate_k9() { # 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:]]*pedigree[[:space:]]*=.*\{ ]] || \ + [[ -n "$pedigree_alias" && "$line" =~ ^[[:space:]]*let[[:space:]]+$pedigree_alias[[:space:]]*=[[:space:]]*\{ ]]; then has_pedigree=true in_pedigree=true pedigree_depth=0 diff --git a/.machine_readable/arrival-pack/claude-md.k9.ncl b/.machine_readable/arrival-pack/claude-md.k9.ncl index d3fbb1c..d606ff0 100644 --- a/.machine_readable/arrival-pack/claude-md.k9.ncl +++ b/.machine_readable/arrival-pack/claude-md.k9.ncl @@ -4,7 +4,7 @@ K9! # # claude-md.k9.ncl — k9 contract for the CLAUDE.md arrival-pack drift check. # -# Yard-tier (pure evaluation / comparison; no exec, no network, no FS write): +# Hunt-tier (guarded execution; no network, writes an intermediate data file): # the check regenerates the arrival-pack region from this repo's a2ml and asserts # the committed CLAUDE.md region byte-matches it. The runnable side is verify.sh; # writing (Hunt-tier) is generate.sh. This record documents and validates the @@ -15,11 +15,12 @@ K9! contractile_verb = "trust", # provenance: the view must equal its source semantics = "projection-fidelity", security = { - leash = 'Yard, - trust_level = "read-only comparison", + leash = 'Hunt, + trust_level = "guarded drift verification", allow_network = false, - allow_filesystem_write = false, + allow_filesystem_write = true, allow_subprocess = true, # runs extract.sh + nickel to reproduce the view + signature_required = true, }, metadata = { name = "claude-md-arrival-pack", diff --git a/.machine_readable/coaptation/coapt.k9.ncl b/.machine_readable/coaptation/coapt.k9.ncl index 2c065bf..28909e7 100644 --- a/.machine_readable/coaptation/coapt.k9.ncl +++ b/.machine_readable/coaptation/coapt.k9.ncl @@ -4,7 +4,7 @@ K9! # # coapt.k9.ncl — k9 contract for the coaptation receipt's drift check. # -# Yard-tier (pure evaluation / comparison; no network, no FS write): the check +# Hunt-tier (guarded execution; no network, writes intermediate data files): the check # regenerates the coaptation receipt from this repo's contractiles (normative # set-point) and descriptiles (descriptive self-model) and asserts the committed # receipt byte-matches it. The runnable side is verify.sh; the writer (Hunt-tier, @@ -16,11 +16,12 @@ K9! contractile_verb = "trust", # provenance: the reading must equal its sources semantics = "coaptation-fidelity", security = { - leash = 'Yard, - trust_level = "read-only comparison", + leash = 'Hunt, + trust_level = "guarded drift verification", allow_network = false, - allow_filesystem_write = false, + allow_filesystem_write = true, allow_subprocess = true, # runs the extractors + nickel to reproduce the reading + signature_required = true, }, metadata = { name = "coaptation", diff --git a/.machine_readable/contractiles/bust/bust.k9.ncl b/.machine_readable/contractiles/bust/bust.k9.ncl index cef8427..cc8032e 100644 --- a/.machine_readable/contractiles/bust/bust.k9.ncl +++ b/.machine_readable/contractiles/bust/bust.k9.ncl @@ -51,6 +51,7 @@ let base = import "../_base.ncl" in allow_filesystem_write = false, allow_subprocess = true, probe_scope = 'read_only, + signature_required = true, }, }, diff --git a/.machine_readable/contractiles/intend/intend.k9.ncl b/.machine_readable/contractiles/intend/intend.k9.ncl index 7c9220b..d8d83b7 100644 --- a/.machine_readable/contractiles/intend/intend.k9.ncl +++ b/.machine_readable/contractiles/intend/intend.k9.ncl @@ -64,6 +64,7 @@ let base = import "../_base.ncl" in allow_network = false, allow_filesystem_write = false, # evidence sinks are indirected allow_subprocess = true, + signature_required = true, }, }, diff --git a/.machine_readable/contractiles/must/must.k9.ncl b/.machine_readable/contractiles/must/must.k9.ncl index 6de4df2..e6327ed 100644 --- a/.machine_readable/contractiles/must/must.k9.ncl +++ b/.machine_readable/contractiles/must/must.k9.ncl @@ -73,6 +73,7 @@ let base = import "../_base.ncl" in 'external_api, 'exploit_attempt, # that's trust's safe_hacking territory ], + signature_required = true, }, }, diff --git a/.machine_readable/contractiles/trust/trust.k9.ncl b/.machine_readable/contractiles/trust/trust.k9.ncl index 0657470..32b48ed 100644 --- a/.machine_readable/contractiles/trust/trust.k9.ncl +++ b/.machine_readable/contractiles/trust/trust.k9.ncl @@ -78,6 +78,7 @@ let base = import "../_base.ncl" in allow_subprocess = true, authorised_probes_only = true, # probe section explicitly lists allowed targets + probe classes probe_scope_enforcement = 'this_repo_only, # probes NEVER hit external systems + signature_required = true, }, }, diff --git a/.machine_readable/self-validating/methodology-guard.k9.ncl b/.machine_readable/self-validating/methodology-guard.k9.ncl index 796b0eb..6212efb 100644 --- a/.machine_readable/self-validating/methodology-guard.k9.ncl +++ b/.machine_readable/self-validating/methodology-guard.k9.ncl @@ -9,6 +9,24 @@ K9! # Usage: k9 validate methodology-guard let methodology_guard = { + pedigree = { + schema_version = "1.0.0", + component_type = "validated-config", + security = { + leash = 'Yard, + trust_level = "validated-config", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "methodology-guard", + version = "1.0.0", + description = "Validates that agent work respects declared methodology constraints", + author = "{{AUTHOR}} <{{AUTHOR_EMAIL}}>", + }, + }, + name = "methodology-guard", version = "1.0.0", description = "Validates that agent work respects declared methodology constraints", diff --git a/container/stapeln/deploy.k9.ncl b/container/stapeln/deploy.k9.ncl index 8182fe8..f74c363 100644 --- a/container/stapeln/deploy.k9.ncl +++ b/container/stapeln/deploy.k9.ncl @@ -40,6 +40,7 @@ let component_pedigree = { # L3: The Leash — Security # ───────────────────────────────────────────────────────────── security = { + leash = 'Hunt, trust_level = 'Hunt, allow_network = true, allow_filesystem_write = true, diff --git a/crates/squabble-core/src/gate.rs b/crates/squabble-core/src/gate.rs index a28d2df..1e424da 100644 --- a/crates/squabble-core/src/gate.rs +++ b/crates/squabble-core/src/gate.rs @@ -26,7 +26,6 @@ pub enum CheckRun { Passed, } - /// Why a required context shows [`CheckRun::Missing`]. /// /// `Missing` is the gate's most common stuck state and its least actionable one: @@ -234,9 +233,15 @@ mod tests { #[test] fn missing_cause_is_optional_and_does_not_alter_gate_state() { // A diagnosis explains a stuck gate; it must never move it. - let undiagnosed = Gate::new(vec![RequiredCheck::new("scan / gitleaks", CheckRun::Missing)]); - let diagnosed = Gate::new(vec![RequiredCheck::new("scan / gitleaks", CheckRun::Missing) - .with_cause(MissingCause::DeadActionPin)]); + let undiagnosed = Gate::new(vec![RequiredCheck::new( + "scan / gitleaks", + CheckRun::Missing, + )]); + let diagnosed = Gate::new(vec![RequiredCheck::new( + "scan / gitleaks", + CheckRun::Missing, + ) + .with_cause(MissingCause::DeadActionPin)]); assert_eq!(undiagnosed.evaluate(), GateState::Blocked); assert_eq!(diagnosed.evaluate(), GateState::Blocked); assert_eq!(diagnosed.evaluate(), undiagnosed.evaluate()); @@ -244,7 +249,9 @@ mod tests { #[test] fn remedy_is_offered_only_for_diagnosed_missing_checks() { - assert!(RequiredCheck::new("x", CheckRun::Missing).remedy().is_none()); + assert!(RequiredCheck::new("x", CheckRun::Missing) + .remedy() + .is_none()); assert!(RequiredCheck::new("x", CheckRun::Passed) .with_cause(MissingCause::NoSuchJob) .remedy() @@ -270,7 +277,11 @@ mod tests { RequiredCheck::new("a", CheckRun::Passed), RequiredCheck::new("b", CheckRun::Missing).with_cause(cause), ]); - assert_ne!(g.evaluate(), GateState::Green, "{cause:?} must not reach Green"); + assert_ne!( + g.evaluate(), + GateState::Green, + "{cause:?} must not reach Green" + ); } } } diff --git a/tests/e2e/template_instantiation_test.sh b/tests/e2e/template_instantiation_test.sh index 73a7d8d..8d11346 100755 --- a/tests/e2e/template_instantiation_test.sh +++ b/tests/e2e/template_instantiation_test.sh @@ -112,6 +112,8 @@ replace_placeholder() { } # Replace in all text files +export TEST_REPO_NAME TEST_OWNER TEST_FORGE TEST_PROJECT_NAME \ + TEST_DESCRIPTION TEST_PRIMARY_LANGUAGE TEST_AUTHOR TEST_AUTHOR_EMAIL find "$TEST_REPO_PATH" -type f \ \( -name "*.md" -o -name "*.adoc" -o -name "*.a2ml" -o -name "*.zig" -o -name "*.idr" \ -o -name "Justfile" -o -name "Containerfile" -o -name "*.yml" -o -name "*.yaml" \ @@ -137,7 +139,7 @@ find "$TEST_REPO_PATH" -type f \ sed -i "s|$placeholder|$value|g" "$file" fi done - ' _ "$file" + ' _ {} \; log_pass "All placeholder tokens replaced"