Skip to content
19 changes: 17 additions & 2 deletions .githooks/validate-k9.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {…},`
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions .machine_readable/arrival-pack/claude-md.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand Down
9 changes: 5 additions & 4 deletions .machine_readable/coaptation/coapt.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand Down
1 change: 1 addition & 0 deletions .machine_readable/contractiles/bust/bust.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ let base = import "../_base.ncl" in
allow_filesystem_write = false,
allow_subprocess = true,
probe_scope = 'read_only,
signature_required = true,
},
},

Expand Down
1 change: 1 addition & 0 deletions .machine_readable/contractiles/intend/intend.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},

Expand Down
1 change: 1 addition & 0 deletions .machine_readable/contractiles/must/must.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ let base = import "../_base.ncl" in
'external_api,
'exploit_attempt, # that's trust's safe_hacking territory
],
signature_required = true,
},
},

Expand Down
1 change: 1 addition & 0 deletions .machine_readable/contractiles/trust/trust.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},

Expand Down
18 changes: 18 additions & 0 deletions .machine_readable/self-validating/methodology-guard.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions container/stapeln/deploy.k9.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ let component_pedigree = {
# L3: The Leash — Security
# ─────────────────────────────────────────────────────────────
security = {
leash = 'Hunt,
trust_level = 'Hunt,
allow_network = true,
allow_filesystem_write = true,
Expand Down
23 changes: 17 additions & 6 deletions crates/squabble-core/src/gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -234,17 +233,25 @@ 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());
}

#[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()
Expand All @@ -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"
);
}
}
}
4 changes: 3 additions & 1 deletion tests/e2e/template_instantiation_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand All @@ -137,7 +139,7 @@ find "$TEST_REPO_PATH" -type f \
sed -i "s|$placeholder|$value|g" "$file"
fi
done
' _ "$file"
' _ {} \;
Comment on lines 139 to +142

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

The substitution logic is still a no-op. Because the subshell string is single-quoted, the parent shell's $placeholder and $value variables are not expanded. Within the subshell, you must use "$1" to refer to the file path passed by find, as $file is undefined. Recommendation: Pass the variables as arguments to the subshell (e.g., sh -c '...' _ "$placeholder" "$value" "{}") or use double quotes with appropriate escaping. Additionally, using '+' instead of ';' would be more efficient for large file sets.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you address this finding


log_pass "All placeholder tokens replaced"

Expand Down
Loading